LP学长

LP学长 查看完整档案

西安编辑西北工业大学  |  软件工程 编辑  |  填写所在公司/组织填写个人主网站
编辑

想精通Java的学生党

个人动态

LP学长 收藏了文章 · 10月22日

编写自己的代码库(javascript常用实例的实现与封装)

1.前言

因为公司最近项目比较忙,没那么多空余的事件写文章了,所以这篇文章晚了几天发布。但是这也没什么关系,不过该来的,总是会来的。
好了,其他的不多说的,大家在开发的时候应该知道,有很多常见的实例操作。比如数组去重,关键词高亮,打乱数组等。这些操作,代码一般不会很多,实现的逻辑也不会很难,下面的代码,我解释就不解释太多了,打上注释,相信大家就会懂了。但是,用的地方会比较,如果项目有哪个地方需要用,如果重复写的话,就是代码沉余,开发效率也不用,复用基本就是复制粘贴!这样是一个很不好的习惯,大家可以考虑一下把一些常见的操作封装成函数,调用的时候,直接调用就好!
源码都放在github上了,大家想以后以后有什么修改或者增加的,欢迎大家来star一下ec-do

2.字符串操作

2-1去除字符串空格

//去除空格  type 1-所有空格  2-前后空格  3-前空格 4-后空格
function trim(str,type){
    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;
    }
}

2-2字母大小写切换

/*type
1:首字母大写   
2:首页母小写
3:大小写转换
4:全部大写
5:全部小写
 * */
//changeCase('asdasd',1)
//Asdasd
function changeCase(str,type)
{
    function ToggleCase(str) {
        var itemText = ""
        str.split("").forEach(
            function (item) {
                if (/^([a-z]+)/.test(item)) {
                    itemText += item.toUpperCase();
                }
                else if (/^([A-Z]+)/.test(item)) {
                    itemText += item.toLowerCase();
                }
                else{
                    itemText += item;
                }
            });
        return itemText;
    }

    switch (type) {
        case 1:
            return str.replace(/^(\w)(\w+)/, function (v, v1, v2) {
                return v1.toUpperCase() + v2.toLowerCase();
            });
        case 2:
            return str.replace(/^(\w)(\w+)/, function (v, v1, v2) {
                return v1.toLowerCase() + v2.toUpperCase();
            });
        case 3:
            return ToggleCase(str);
        case 4:
            return str.toUpperCase();
        case 5:
            return str.toLowerCase();
        default:
            return str;
    }
}

2-3字符串循环复制

//repeatStr(str->字符串, count->次数)
//repeatStr('123',3)
//"123123123"
function repeatStr(str, count) {
    var text = '';
    for (var i = 0; i < count; i++) {
        text += str;
    }
    return text;
}

2-4字符串替换

//字符串替换(字符串,要替换的字符,替换成什么)
function replaceAll(str,AFindText,ARepText){
   raRegExp = new RegExp(AFindText,"g");
   return str.replace(raRegExp,ARepText);
}

2-5替换*

//replaceStr(字符串,字符格式, 替换方式,替换的字符(默认*))
function replaceStr(str, regArr, type,ARepText) {
    var regtext = '', Reg = null,replaceText=ARepText||'*';
    //replaceStr('18819322663',[3,5,3],0)
    //188*****663
    //repeatStr是在上面定义过的(字符串循环复制),大家注意哦
    if (regArr.length === 3 && type === 0) {
        regtext = '(\\w{' + regArr[0] + '})\\w{' + regArr[1] + '}(\\w{' + regArr[2] + '})'
        Reg = new RegExp(regtext);
        var replaceCount = repeatStr(replaceText, regArr[1]);
        return str.replace(Reg, '$1' + replaceCount + '$2')
    }
    //replaceStr('asdasdasdaa',[3,5,3],1)
    //***asdas***
    else if (regArr.length === 3 && type === 1) {
        regtext = '\\w{' + regArr[0] + '}(\\w{' + regArr[1] + '})\\w{' + regArr[2] + '}'
        Reg = new RegExp(regtext);
        var replaceCount1 = repeatSte(replaceText, regArr[0]);
        var replaceCount2 = repeatSte(replaceText, regArr[2]);
        return str.replace(Reg, replaceCount1 + '$1' + replaceCount2)
    }
    //replaceStr('1asd88465asdwqe3',[5],0)
    //*****8465asdwqe3
    else if (regArr.length === 1 && type == 0) {
        regtext = '(^\\w{' + regArr[0] +  '})'
        Reg = new RegExp(regtext);
        var replaceCount = repeatSte(replaceText, regArr[0]);
        return str.replace(Reg, replaceCount)
    }
    //replaceStr('1asd88465asdwqe3',[5],1,'+')
    //"1asd88465as+++++"
    else if (regArr.length === 1 && type == 1) {
        regtext = '(\\w{' + regArr[0] +  '}$)'
        Reg = new RegExp(regtext);
        var replaceCount = repeatSte(replaceText, regArr[0]);
        return str.replace(Reg, replaceCount)
    }
}

2-6检测字符串

//checkType('165226226326','phone')
//false
//大家可以根据需要扩展
function checkType (str, type) {
    switch (type) {
        case 'email':
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        case 'phone':
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        case 'tel':
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.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);
        default :
            return true;
    }
}

2-7检测密码强度

//checkPwd('12asdASAD')
//3(强度等级为3)
function checkPwd(str) {
    var nowLv = 0;
    if (str.length < 6) {
        return nowLv
    }
    ;
    if (/[0-9]/.test(str)) {
        nowLv++
    }
    ;
    if (/[a-z]/.test(str)) {
        nowLv++
    }
    ;
    if (/[A-Z]/.test(str)) {
        nowLv++
    }
    ;
    if (/[\.|-|_]/.test(str)) {
        nowLv++
    }
    ;
    return nowLv;
}

2-8随机码(toString详解

//count取值范围0-36

//randomNumber(10)
//"2584316588472575"

//randomNumber(14)
//"9b405070dd00122640c192caab84537"

//Math.random().toString(36).substring(2);
//"83vhdx10rmjkyb9"
function randomNumber(count){
   return Math.random().toString(count).substring(2);
}

2-9查找字符串

可能标题会有点误导,下面我就简单说明一个需求,在字符串'sad44654blog5a1sd67as9dablog4s5d16zxc4sdweasjkblogwqepaskdkblogahseiuadbhjcibloguyeajzxkcabloguyiwezxc967'中找出'blog'的出现次数。代码如下

function countStr (str,strSplit){
    return str.split(strSplit).length-1
}
var strTest='sad44654blog5a1sd67as9dablog4s5d16zxc4sdweasjkblogwqepaskdkblogahseiuadbhjcibloguyeajzxkcabloguyiwezxc967'
//countStr(strTest,'blog')
//6

3.数组操作

3-1数组去重

这个方法是在太多了,我之前写的文章(js数组操作--使用迭代方法替代for循环,js关键词变色,数组打乱,数组去重的实现和封装)也有提到,我今天这里就写一种之前没用过的方法。

//ES6新增的Set数据结构,类似于数组,但是里面的元素都是唯一的 ,其构造函数可以接受一个数组作为参数
//let arr=[1,2,1,2,6,3,5,69,66,7,2,1,4,3,6,8,9663,8]
//let set = new Set(array);
//{1,2,6,3,5,69,66,7,4,8,9663}
//ES6中Array新增了一个静态方法from,可以把类似数组的对象转换为数组
//Array.from(set)
//[1,2,6,3,5,69,66,7,4,8,9663]
function removeRepeatArray(arr){
    return Array.from(new Set(arr))
}

3-2数组顺序打乱

function upsetArr(arr){
    return arr.sort(function(){ return Math.random() - 0.5});
}

3-3数组最大值最小值

//这一块的封装,主要是针对数字类型的数组
function maxArr(arr){
    return Math.max.apply(null,arr);
}
function minArr(arr){
    return Math.min.apply(null,arr);
}

3-4数组求和,平均值

//这一块的封装,主要是针对数字类型的数组
//求和
function sumArr(arr){
    var sumText=0;
    for(var i=0,len=arr.length;i<len;i++){
        sumText+=arr[i];
    }
    return sumText
}
//平均值,小数点可能会有很多位,这里不做处理,处理了使用就不灵活了!
function covArr(arr){
    var sumText=sumArr(arr);
    var covText=sumText/arr.length;
    return covText
}

3-5从数组中随机获取元素

//randomOne([1,2,3,6,8,5,4,2,6])
//2
//randomOne([1,2,3,6,8,5,4,2,6])
//8
//randomOne([1,2,3,6,8,5,4,2,6])
//8
//randomOne([1,2,3,6,8,5,4,2,6])
//1
function randomOne(arr) {
    return arr[Math.floor(Math.random() * arr.length)];
}

3-6返回数组(字符串)一个元素出现的次数

//getEleCount('asd56+asdasdwqe','a')
//3
//getEleCount([1,2,3,4,5,66,77,22,55,22],22)
//2
function getEleCount (obj, ele) {
    var num = 0;
    for (var i = 0, len = obj.length; i < len; i++) {
        if (ele == obj[i]) {
            num++;
        }
    }
    return num;
}

3-7返回数组(字符串)出现最多的几次元素和出现次数

//arr, rank->长度,默认为数组长度,ranktype,排序方式,默认降序
function getCount(arr, rank,ranktype){ 
    var obj = {}, k, arr1 = []
    //记录每一元素出现的次数
    for (var i = 0, len = arr.length; i < len; i++) {
        k = arr[i];
        if (obj[k]) {
            obj[k]++;
        }
        else {
            obj[k] = 1;
        }
    }
    //保存结果{el-'元素',count-出现次数}
    for (var o in obj) {
        arr1.push({el: o, count: obj[o]});
    }
    //排序(降序)
    arr1.sort(function (n1, n2) {
        return n2.count - n1.count
    });
    //如果ranktype为1,则为升序,反转数组
    if(ranktype===1){
        arr1=arr1.reverse();
    }
    var rank1 = rank || arr1.length;
    return arr1.slice(0,rank1);
}

getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2])
默认情况,返回所有元素出现的次数
clipboard.png

getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],3)
传参(rank=3),只返回出现次数排序前三的
clipboard.png

getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],null,1)
传参(ranktype=1,rank=null),升序返回所有元素出现次数
clipboard.png

getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],3,1)
传参(rank=3,ranktype=1),只返回出现次数排序(升序)前三的
clipboard.png

3-8得到n1-n2下标的数组

//getArrayNum([0,1,2,3,4,5,6,7,8,9],5,9)
//[5, 6, 7, 8, 9]

//getArrayNum([0,1,2,3,4,5,6,7,8,9],2) 不传第二个参数,默认返回从n1到数组结束的元素
//[2, 3, 4, 5, 6, 7, 8, 9]
function getArrayNum(arr,n1,n2){
    var arr1=[],len=n2||arr.length-1;
    for(var i=n1;i<=len;i++){
        arr1.push(arr[i])
    }
    return arr1;
}

3-9筛选数组

/删除值为'val'的数组元素
//removeArrayForValue(['test','test1','test2','test','aaa'],'test','%')
//["aaa"]   带有'test'的都删除
    
//removeArrayForValue(['test','test1','test2','test','aaa'],'test')
//["test1", "test2", "aaa"]  //数组元素的值全等于'test'才被删除
function removeArrayForValue(arr,val,type){
    return arr.filter(function (item) {
        return type? item.indexOf(val) === -1 : item !== val
    })
}

4.基础DOM操作

这个部分代码其实参考jquery的一些函数写法,唯一区别就是调用不用,参数一样.
比如下面的栗子

//设置对象内容
jquery:$('#xxx').html('hello world');
现在:html(document.getElementById('xxx'),'hello world')
//获取对象内容
jquery:$('#xxx').html();
现在:html(document.getElementById('xxx'))

4-1检测对象是否有哪个类名

function hasClass(obj,classStr){ 
    var arr=obj.className.split(/\s+/); //这个正则表达式是因为class可以有多个,判断是否包含 
    return (arr.indexOf(classStr)==-1)?false:true;
},

4-2添加类名

function addClass(obj,classStr){
    if (!this.hasClass(obj,classStr)){obj.className += " " + classStr};
},

4-3删除类名

function removeClass(obj,classStr){
    if (this.hasClass(obj,classStr)) {
        var reg = new RegExp('(\\s|^)' + classStr + '(\\s|$)');
        obj.className = obj.className.replace(reg, '');
    }
},

4-4替换类名("被替换的类名","替换的类名")

function replaceClass(obj,newName,oldName) {
    removeClass(obj,oldName);
    addClass(obj,newName);
}

4-5获取兄弟节点

function siblings(obj){
    var a=[];//定义一个数组,用来存o的兄弟元素 
    var p=obj.previousSibling; 
    while(p){//先取o的哥哥们 判断有没有上一个哥哥元素,如果有则往下执行 p表示previousSibling 
        if(p.nodeType===1){ 
        a.push(p); 
        } 
        p=p.previousSibling//最后把上一个节点赋给p 
    } 
    a.reverse()//把顺序反转一下 这样元素的顺序就是按先后的了 
    var n=obj.nextSibling;//再取o的弟弟 
    while(n){//判断有没有下一个弟弟结点 n是nextSibling的意思 
        if(n.nodeType===1){ 
            a.push(n); 
        } 
        n=n.nextSibling; 
    }
    return a;
},

4-6设置样式

function css(obj,json){
    for(var attr in json){
        obj.style[attr]=json[attr];
    }
}

4-7设置文本内容

function html(obj){
    if(arguments.length==0){
        return this.innerHTML;
    }
    else if(arguments.length==1){
        this.innerHTML=arguments[0];
    }
}

4-8显示隐藏


function show(obj){
    obj.style.display="";
}
function hide(obj){
    obj.style.display="none";
}

5.其他操作

5-1cookie

//cookie
//设置cookie
function setCookie(name,value,iDay){
    var oDate=new Date();
    oDate.setDate(oDate.getDate()+iDay);
    document.cookie=name+'='+value+';expires='+oDate;
}
//获取cookie
function 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
function removeCookie(name){
    setCookie(name,1,-1);
}

5-2清除对象中值为空的属性

//filterParams({a:"",b:null,c:"010",d:123})
//Object {c: "010", d: 123}
function filterParams(obj){
    let _newPar = {};
    for (let key in obj) {
        if ((obj[key] === 0 || obj[key]) && obj[key].toString().replace(/(^\s*)|(\s*$)/g, '') !== '') {
            _newPar[key] = obj[key];
        }
    }
    return _newPar;
}

5-3现金额大写转换函数

//upDigit(168752632)
//"人民币壹亿陆仟捌佰柒拾伍万贰仟陆佰叁拾贰元整"
//upDigit(1682)
//"人民币壹仟陆佰捌拾贰元整"
//upDigit(-1693)
//"欠人民币壹仟陆佰玖拾叁元整"
function upDigit(n)   
{  
    var fraction = ['角', '分','厘'];  
    var digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];  
    var unit = [ ['元', '万', '亿'], ['', '拾', '佰', '仟']  ];  
    var head = n < 0? '欠人民币': '人民币';  
    n = Math.abs(n);  
    var s = '';  
    for (var i = 0; i < fraction.length; i++)   
    {
        s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, ''); 
    } 
    s = s || '整';  
    n = Math.floor(n);  
    for (var i = 0; i < unit[0].length && n > 0; i++)   
    {  
        var p = '';  
        for (var j = 0; j < unit[1].length && n > 0; j++)   
        {  
            p = digit[n % 10] + unit[1][j] + p; 
            n = Math.floor(n / 10);
        }
        //s = p.replace(/(零.)*零$/, '').replace(/^$/, '零')+ unit[0][i] + s; 
        s = p+ unit[0][i] + s;
    }
    return head + s.replace(/(零.)*零元/, '元').replace(/(零.)+/g, '零').replace(/^整$/, '零元整');
} 

5-4获取,设置url参数

//获取url参数
//getUrlPrmt('segmentfault.com/write?draftId=122000011938')
//Object{draftId: "122000011938"}
function getUrlPrmt(url) {
    url = url ? url : window.location.href;
    let _pa = url.substring(url.indexOf('?') + 1), _arrS = _pa.split('&'), _rs = {};
    for (let i = 0, _len = _arrS.length; i < _len; i++) {
        let pos = _arrS[i].indexOf('=');
        if (pos == -1) {
            continue;
        }
        let name = _arrS[i].substring(0, pos), value = window.decodeURIComponent(_arrS[i].substring(pos + 1));
        _rs[name] = value;
    }
    return _rs;
}

//设置url参数
//setUrlPrmt({'a':1,'b':2})
//a=1&b=2
function setUrlPrmt(obj) {
    let _rs = [];
    for (let p in obj) {
        if (obj[p] != null && obj[p] != '') {
            _rs.push(p + '=' + obj[p])
        }
    }
    return _rs.join('&');
}

5-5随机返回一个范围的数字


function randomNumber(n1,n2){
    //randomNumber(5,10)
    //返回5-10的随机整数,包括5,10
    if(arguments.length===2){
        return Math.round(n1+Math.random()*(n2-n1));
    }
    //randomNumber(10)
    //返回0-10的随机整数,包括0,10
    else if(arguments.length===1){
        return Math.round(Math.random()*n1)
    }
    //randomNumber()
    //返回0-255的随机整数,包括0,255
    else{
        return Math.round(Math.random()*255)
    }  
}

5-6随进产生颜色

function randomColor(){
    //randomNumber是上面定义的函数
    //写法1
    return 'rgb(' + randomNumber(255) + ',' + randomNumber(255) + ',' + randomNumber(255) + ')';
    
    //写法2
    return '#'+Math.random().toString(16).substring(2).substr(0,6);
    
    //写法3
    var color='#';
    for(var i=0;i<6;i++){
        color+='0123456789abcdef'[randomNumber(15)];
    }
    return color;
}

//这种写法,偶尔会有问题。大家得注意哦
//Math.floor(Math.random()*0xffffff).toString(16);
clipboard.png

5-7Date日期时间部分

//到某一个时间的倒计时
//getEndTime('2017/7/22 16:0:0')
//"剩余时间6天 2小时 28 分钟20 秒"
function getEndTime(endTime){
    var startDate=new Date();  //开始时间,当前时间
    var endDate=new Date(endTime); //结束时间,需传入时间参数
    var t=endDate.getTime()-startDate.getTime();  //时间差的毫秒数
    var d=0,h=0,m=0,s=0;
    if(t>=0){
      d=Math.floor(t/1000/3600/24);
      h=Math.floor(t/1000/60/60%24);
      m=Math.floor(t/1000/60%60);
      s=Math.floor(t/1000%60);
    } 
    return "剩余时间"+d+"天 "+h+"小时 "+m+" 分钟"+s+" 秒";
}

5-8适配rem

这个适配的方法很多,我就写我自己用的方法。大家也可以去我回答过得一个问题那里看更详细的说明!移动端适配问题

function getFontSize(){
    var doc=document,win=window;
    var docEl = doc.documentElement,
    resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
    recalc = function () {
        var clientWidth = docEl.clientWidth;
        if (!clientWidth) return;
        //如果屏幕大于750(750是根据我效果图设置的,具体数值参考效果图),就设置clientWidth=750,防止font-size会超过100px
        if(clientWidth>750){clientWidth=750}
        //设置根元素font-size大小
        docEl.style.fontSize = 100 * (clientWidth / 750) + 'px';
    };
    //屏幕大小改变,或者横竖屏切换时,触发函数
    win.addEventListener(resizeEvt, recalc, false);
    //文档加载完成时,触发函数
    doc.addEventListener('DOMContentLoaded', recalc, false);
}
//使用方式很简单,比如效果图上,有张图片。宽高都是100px;
//样式写法就是
img{
    width:1rem;
    height:1rem;
}
//这样的设置,比如在屏幕宽度大于等于750px设备上,1rem=100px;图片显示就是宽高都是100px
//比如在iphone6(屏幕宽度:375)上,375/750*100=50px;就是1rem=50px;图片显示就是宽高都是50px;

6.封装成形

写了这么多的操作,小伙伴应该发现了一问题,全局函数太多了

//这样的话,封装了几个操作,就增加了几个全局函数,污染了全局变量,在开发中应该尽量避免全局变量。不说别的,如果一个项目,几个人开发,很有可能会造成命名的冲突。

function setUrlPrmt(obj){..}
function getUrlPrmt(url){..}
function upsetArr(obj){..}


//所以,建议的封装姿势是
var myJS={
    setUrlPrmt:function(obj){..},
    getUrlPrmt:function(url){..},
    upsetArr:function(arr){..},
}
这样就算别人也写这样的函数,也不会造成冲突。全局变量也只有一个,加上别人也不会很多!
var otherJS={
    setUrlPrmt:function(obj){..},
    getUrlPrmt:function(url){..},
    upsetArr:function(arr){..},
}


//最后,封装的效果是
var myJS={
    //去除字符串空格
    trim:function(obj){..},
    //字母大小写切换
    changeCase:function(obj){..},
    //字符串循环复制
    repeatStr:function(str){..},
    .....
}

//如果是es6的模块化开发,大家也可以
let myJS={
    //去除字符串空格
    trim:function(obj){..},
    //字母大小写切换
    changeCase:function(obj){..},
    //字符串循环复制
    repeatStr:function(str){..},
    .....
}
//暴露模块,里面的方式大家也可以用es6方式实现,代码至少会少一点!
export {myJS}
可能有小伙伴会有疑问,这样封装,调用有点麻烦,为什么不直接在原型上面封装,调用方便。比如下面的栗子!
String.prototype.trim=function(type){
    switch (type){
        case 1:return this.replace(/\s+/g,"");
        case 2:return this.replace(/(^\s*)|(\s*$)/g, "");
        case 3:return this.replace(/(^\s*)/g, "");
        case 4:return this.replace(/(\s*$)/g, "");
        default:return this;
    }
}
//'  12345 6 8 96  '.trim(1)
//"123456896"
//比这样trim('  12345 6 8 96  ',1)调用方便。
//但是,这样是不推荐的做法,这样就污染了原生对象String,别人创建的String也会被污染,造成不必要的开销。
//更可怕的是,万一自己命名的跟原生的方法重名了,就被覆盖原来的方法了
//String.prototype.substr=function(){console.log('asdasd')}  
//'asdasdwe46546'.substr()
//asdasd 
//substr方法有什么作用,大家应该知道,不知道的可以去w3c看下

所以在原生对象原型的修改很不推荐!至少很多的公司禁止这样操作!

7.小结

这篇文章,写了很久了,几个小时了,因为我写这篇文章,我也是重新改我以前代码的,因为我以前写的代码,功能一样,代码比较多,现在是边想边改边写,还要自己测试(之前的代码for循环很多,现在有很多简洁的写法代替)。加上最近公司比较忙,所以这一篇文章也是花了几天才整理完成。
源码都放在github上了,大家想以后以后有什么修改或者增加的,欢迎大家来star一下ec-do
我自己封装这个,并不是我有造轮子的习惯,而是:
1,都是一些常用,但是零散的小实例,网上基本没有插件。
2,因为零散的小实例,涉及到的有字符串,数组,对象等类型,就算找到插件,在项目引入的很有可能不止一个插件。
3.都是简单的代码,封装也不难。维护也简单。
废话就不多说了,上面的只是我自己在开发中常用到,希望能帮到小伙伴们,最理想就是这篇文章能起到一个抛砖引玉的作用,就是说,如果觉得还有什么操作是常用的,或者觉得我哪里写得不好的,也欢迎指出,让大家相互帮助,相互学习。

-------------------------华丽的分割线--------------------
想了解更多,关注关注我的微信公众号:守候书阁

clipboard.png

查看原文

LP学长 赞了文章 · 10月22日

编写自己的代码库(javascript常用实例的实现与封装)

1.前言

因为公司最近项目比较忙,没那么多空余的事件写文章了,所以这篇文章晚了几天发布。但是这也没什么关系,不过该来的,总是会来的。
好了,其他的不多说的,大家在开发的时候应该知道,有很多常见的实例操作。比如数组去重,关键词高亮,打乱数组等。这些操作,代码一般不会很多,实现的逻辑也不会很难,下面的代码,我解释就不解释太多了,打上注释,相信大家就会懂了。但是,用的地方会比较,如果项目有哪个地方需要用,如果重复写的话,就是代码沉余,开发效率也不用,复用基本就是复制粘贴!这样是一个很不好的习惯,大家可以考虑一下把一些常见的操作封装成函数,调用的时候,直接调用就好!
源码都放在github上了,大家想以后以后有什么修改或者增加的,欢迎大家来star一下ec-do

2.字符串操作

2-1去除字符串空格

//去除空格  type 1-所有空格  2-前后空格  3-前空格 4-后空格
function trim(str,type){
    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;
    }
}

2-2字母大小写切换

/*type
1:首字母大写   
2:首页母小写
3:大小写转换
4:全部大写
5:全部小写
 * */
//changeCase('asdasd',1)
//Asdasd
function changeCase(str,type)
{
    function ToggleCase(str) {
        var itemText = ""
        str.split("").forEach(
            function (item) {
                if (/^([a-z]+)/.test(item)) {
                    itemText += item.toUpperCase();
                }
                else if (/^([A-Z]+)/.test(item)) {
                    itemText += item.toLowerCase();
                }
                else{
                    itemText += item;
                }
            });
        return itemText;
    }

    switch (type) {
        case 1:
            return str.replace(/^(\w)(\w+)/, function (v, v1, v2) {
                return v1.toUpperCase() + v2.toLowerCase();
            });
        case 2:
            return str.replace(/^(\w)(\w+)/, function (v, v1, v2) {
                return v1.toLowerCase() + v2.toUpperCase();
            });
        case 3:
            return ToggleCase(str);
        case 4:
            return str.toUpperCase();
        case 5:
            return str.toLowerCase();
        default:
            return str;
    }
}

2-3字符串循环复制

//repeatStr(str->字符串, count->次数)
//repeatStr('123',3)
//"123123123"
function repeatStr(str, count) {
    var text = '';
    for (var i = 0; i < count; i++) {
        text += str;
    }
    return text;
}

2-4字符串替换

//字符串替换(字符串,要替换的字符,替换成什么)
function replaceAll(str,AFindText,ARepText){
   raRegExp = new RegExp(AFindText,"g");
   return str.replace(raRegExp,ARepText);
}

2-5替换*

//replaceStr(字符串,字符格式, 替换方式,替换的字符(默认*))
function replaceStr(str, regArr, type,ARepText) {
    var regtext = '', Reg = null,replaceText=ARepText||'*';
    //replaceStr('18819322663',[3,5,3],0)
    //188*****663
    //repeatStr是在上面定义过的(字符串循环复制),大家注意哦
    if (regArr.length === 3 && type === 0) {
        regtext = '(\\w{' + regArr[0] + '})\\w{' + regArr[1] + '}(\\w{' + regArr[2] + '})'
        Reg = new RegExp(regtext);
        var replaceCount = repeatStr(replaceText, regArr[1]);
        return str.replace(Reg, '$1' + replaceCount + '$2')
    }
    //replaceStr('asdasdasdaa',[3,5,3],1)
    //***asdas***
    else if (regArr.length === 3 && type === 1) {
        regtext = '\\w{' + regArr[0] + '}(\\w{' + regArr[1] + '})\\w{' + regArr[2] + '}'
        Reg = new RegExp(regtext);
        var replaceCount1 = repeatSte(replaceText, regArr[0]);
        var replaceCount2 = repeatSte(replaceText, regArr[2]);
        return str.replace(Reg, replaceCount1 + '$1' + replaceCount2)
    }
    //replaceStr('1asd88465asdwqe3',[5],0)
    //*****8465asdwqe3
    else if (regArr.length === 1 && type == 0) {
        regtext = '(^\\w{' + regArr[0] +  '})'
        Reg = new RegExp(regtext);
        var replaceCount = repeatSte(replaceText, regArr[0]);
        return str.replace(Reg, replaceCount)
    }
    //replaceStr('1asd88465asdwqe3',[5],1,'+')
    //"1asd88465as+++++"
    else if (regArr.length === 1 && type == 1) {
        regtext = '(\\w{' + regArr[0] +  '}$)'
        Reg = new RegExp(regtext);
        var replaceCount = repeatSte(replaceText, regArr[0]);
        return str.replace(Reg, replaceCount)
    }
}

2-6检测字符串

//checkType('165226226326','phone')
//false
//大家可以根据需要扩展
function checkType (str, type) {
    switch (type) {
        case 'email':
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        case 'phone':
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        case 'tel':
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.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);
        default :
            return true;
    }
}

2-7检测密码强度

//checkPwd('12asdASAD')
//3(强度等级为3)
function checkPwd(str) {
    var nowLv = 0;
    if (str.length < 6) {
        return nowLv
    }
    ;
    if (/[0-9]/.test(str)) {
        nowLv++
    }
    ;
    if (/[a-z]/.test(str)) {
        nowLv++
    }
    ;
    if (/[A-Z]/.test(str)) {
        nowLv++
    }
    ;
    if (/[\.|-|_]/.test(str)) {
        nowLv++
    }
    ;
    return nowLv;
}

2-8随机码(toString详解

//count取值范围0-36

//randomNumber(10)
//"2584316588472575"

//randomNumber(14)
//"9b405070dd00122640c192caab84537"

//Math.random().toString(36).substring(2);
//"83vhdx10rmjkyb9"
function randomNumber(count){
   return Math.random().toString(count).substring(2);
}

2-9查找字符串

可能标题会有点误导,下面我就简单说明一个需求,在字符串'sad44654blog5a1sd67as9dablog4s5d16zxc4sdweasjkblogwqepaskdkblogahseiuadbhjcibloguyeajzxkcabloguyiwezxc967'中找出'blog'的出现次数。代码如下

function countStr (str,strSplit){
    return str.split(strSplit).length-1
}
var strTest='sad44654blog5a1sd67as9dablog4s5d16zxc4sdweasjkblogwqepaskdkblogahseiuadbhjcibloguyeajzxkcabloguyiwezxc967'
//countStr(strTest,'blog')
//6

3.数组操作

3-1数组去重

这个方法是在太多了,我之前写的文章(js数组操作--使用迭代方法替代for循环,js关键词变色,数组打乱,数组去重的实现和封装)也有提到,我今天这里就写一种之前没用过的方法。

//ES6新增的Set数据结构,类似于数组,但是里面的元素都是唯一的 ,其构造函数可以接受一个数组作为参数
//let arr=[1,2,1,2,6,3,5,69,66,7,2,1,4,3,6,8,9663,8]
//let set = new Set(array);
//{1,2,6,3,5,69,66,7,4,8,9663}
//ES6中Array新增了一个静态方法from,可以把类似数组的对象转换为数组
//Array.from(set)
//[1,2,6,3,5,69,66,7,4,8,9663]
function removeRepeatArray(arr){
    return Array.from(new Set(arr))
}

3-2数组顺序打乱

function upsetArr(arr){
    return arr.sort(function(){ return Math.random() - 0.5});
}

3-3数组最大值最小值

//这一块的封装,主要是针对数字类型的数组
function maxArr(arr){
    return Math.max.apply(null,arr);
}
function minArr(arr){
    return Math.min.apply(null,arr);
}

3-4数组求和,平均值

//这一块的封装,主要是针对数字类型的数组
//求和
function sumArr(arr){
    var sumText=0;
    for(var i=0,len=arr.length;i<len;i++){
        sumText+=arr[i];
    }
    return sumText
}
//平均值,小数点可能会有很多位,这里不做处理,处理了使用就不灵活了!
function covArr(arr){
    var sumText=sumArr(arr);
    var covText=sumText/arr.length;
    return covText
}

3-5从数组中随机获取元素

//randomOne([1,2,3,6,8,5,4,2,6])
//2
//randomOne([1,2,3,6,8,5,4,2,6])
//8
//randomOne([1,2,3,6,8,5,4,2,6])
//8
//randomOne([1,2,3,6,8,5,4,2,6])
//1
function randomOne(arr) {
    return arr[Math.floor(Math.random() * arr.length)];
}

3-6返回数组(字符串)一个元素出现的次数

//getEleCount('asd56+asdasdwqe','a')
//3
//getEleCount([1,2,3,4,5,66,77,22,55,22],22)
//2
function getEleCount (obj, ele) {
    var num = 0;
    for (var i = 0, len = obj.length; i < len; i++) {
        if (ele == obj[i]) {
            num++;
        }
    }
    return num;
}

3-7返回数组(字符串)出现最多的几次元素和出现次数

//arr, rank->长度,默认为数组长度,ranktype,排序方式,默认降序
function getCount(arr, rank,ranktype){ 
    var obj = {}, k, arr1 = []
    //记录每一元素出现的次数
    for (var i = 0, len = arr.length; i < len; i++) {
        k = arr[i];
        if (obj[k]) {
            obj[k]++;
        }
        else {
            obj[k] = 1;
        }
    }
    //保存结果{el-'元素',count-出现次数}
    for (var o in obj) {
        arr1.push({el: o, count: obj[o]});
    }
    //排序(降序)
    arr1.sort(function (n1, n2) {
        return n2.count - n1.count
    });
    //如果ranktype为1,则为升序,反转数组
    if(ranktype===1){
        arr1=arr1.reverse();
    }
    var rank1 = rank || arr1.length;
    return arr1.slice(0,rank1);
}

getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2])
默认情况,返回所有元素出现的次数
clipboard.png

getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],3)
传参(rank=3),只返回出现次数排序前三的
clipboard.png

getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],null,1)
传参(ranktype=1,rank=null),升序返回所有元素出现次数
clipboard.png

getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],3,1)
传参(rank=3,ranktype=1),只返回出现次数排序(升序)前三的
clipboard.png

3-8得到n1-n2下标的数组

//getArrayNum([0,1,2,3,4,5,6,7,8,9],5,9)
//[5, 6, 7, 8, 9]

//getArrayNum([0,1,2,3,4,5,6,7,8,9],2) 不传第二个参数,默认返回从n1到数组结束的元素
//[2, 3, 4, 5, 6, 7, 8, 9]
function getArrayNum(arr,n1,n2){
    var arr1=[],len=n2||arr.length-1;
    for(var i=n1;i<=len;i++){
        arr1.push(arr[i])
    }
    return arr1;
}

3-9筛选数组

/删除值为'val'的数组元素
//removeArrayForValue(['test','test1','test2','test','aaa'],'test','%')
//["aaa"]   带有'test'的都删除
    
//removeArrayForValue(['test','test1','test2','test','aaa'],'test')
//["test1", "test2", "aaa"]  //数组元素的值全等于'test'才被删除
function removeArrayForValue(arr,val,type){
    return arr.filter(function (item) {
        return type? item.indexOf(val) === -1 : item !== val
    })
}

4.基础DOM操作

这个部分代码其实参考jquery的一些函数写法,唯一区别就是调用不用,参数一样.
比如下面的栗子

//设置对象内容
jquery:$('#xxx').html('hello world');
现在:html(document.getElementById('xxx'),'hello world')
//获取对象内容
jquery:$('#xxx').html();
现在:html(document.getElementById('xxx'))

4-1检测对象是否有哪个类名

function hasClass(obj,classStr){ 
    var arr=obj.className.split(/\s+/); //这个正则表达式是因为class可以有多个,判断是否包含 
    return (arr.indexOf(classStr)==-1)?false:true;
},

4-2添加类名

function addClass(obj,classStr){
    if (!this.hasClass(obj,classStr)){obj.className += " " + classStr};
},

4-3删除类名

function removeClass(obj,classStr){
    if (this.hasClass(obj,classStr)) {
        var reg = new RegExp('(\\s|^)' + classStr + '(\\s|$)');
        obj.className = obj.className.replace(reg, '');
    }
},

4-4替换类名("被替换的类名","替换的类名")

function replaceClass(obj,newName,oldName) {
    removeClass(obj,oldName);
    addClass(obj,newName);
}

4-5获取兄弟节点

function siblings(obj){
    var a=[];//定义一个数组,用来存o的兄弟元素 
    var p=obj.previousSibling; 
    while(p){//先取o的哥哥们 判断有没有上一个哥哥元素,如果有则往下执行 p表示previousSibling 
        if(p.nodeType===1){ 
        a.push(p); 
        } 
        p=p.previousSibling//最后把上一个节点赋给p 
    } 
    a.reverse()//把顺序反转一下 这样元素的顺序就是按先后的了 
    var n=obj.nextSibling;//再取o的弟弟 
    while(n){//判断有没有下一个弟弟结点 n是nextSibling的意思 
        if(n.nodeType===1){ 
            a.push(n); 
        } 
        n=n.nextSibling; 
    }
    return a;
},

4-6设置样式

function css(obj,json){
    for(var attr in json){
        obj.style[attr]=json[attr];
    }
}

4-7设置文本内容

function html(obj){
    if(arguments.length==0){
        return this.innerHTML;
    }
    else if(arguments.length==1){
        this.innerHTML=arguments[0];
    }
}

4-8显示隐藏


function show(obj){
    obj.style.display="";
}
function hide(obj){
    obj.style.display="none";
}

5.其他操作

5-1cookie

//cookie
//设置cookie
function setCookie(name,value,iDay){
    var oDate=new Date();
    oDate.setDate(oDate.getDate()+iDay);
    document.cookie=name+'='+value+';expires='+oDate;
}
//获取cookie
function 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
function removeCookie(name){
    setCookie(name,1,-1);
}

5-2清除对象中值为空的属性

//filterParams({a:"",b:null,c:"010",d:123})
//Object {c: "010", d: 123}
function filterParams(obj){
    let _newPar = {};
    for (let key in obj) {
        if ((obj[key] === 0 || obj[key]) && obj[key].toString().replace(/(^\s*)|(\s*$)/g, '') !== '') {
            _newPar[key] = obj[key];
        }
    }
    return _newPar;
}

5-3现金额大写转换函数

//upDigit(168752632)
//"人民币壹亿陆仟捌佰柒拾伍万贰仟陆佰叁拾贰元整"
//upDigit(1682)
//"人民币壹仟陆佰捌拾贰元整"
//upDigit(-1693)
//"欠人民币壹仟陆佰玖拾叁元整"
function upDigit(n)   
{  
    var fraction = ['角', '分','厘'];  
    var digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];  
    var unit = [ ['元', '万', '亿'], ['', '拾', '佰', '仟']  ];  
    var head = n < 0? '欠人民币': '人民币';  
    n = Math.abs(n);  
    var s = '';  
    for (var i = 0; i < fraction.length; i++)   
    {
        s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, ''); 
    } 
    s = s || '整';  
    n = Math.floor(n);  
    for (var i = 0; i < unit[0].length && n > 0; i++)   
    {  
        var p = '';  
        for (var j = 0; j < unit[1].length && n > 0; j++)   
        {  
            p = digit[n % 10] + unit[1][j] + p; 
            n = Math.floor(n / 10);
        }
        //s = p.replace(/(零.)*零$/, '').replace(/^$/, '零')+ unit[0][i] + s; 
        s = p+ unit[0][i] + s;
    }
    return head + s.replace(/(零.)*零元/, '元').replace(/(零.)+/g, '零').replace(/^整$/, '零元整');
} 

5-4获取,设置url参数

//获取url参数
//getUrlPrmt('segmentfault.com/write?draftId=122000011938')
//Object{draftId: "122000011938"}
function getUrlPrmt(url) {
    url = url ? url : window.location.href;
    let _pa = url.substring(url.indexOf('?') + 1), _arrS = _pa.split('&'), _rs = {};
    for (let i = 0, _len = _arrS.length; i < _len; i++) {
        let pos = _arrS[i].indexOf('=');
        if (pos == -1) {
            continue;
        }
        let name = _arrS[i].substring(0, pos), value = window.decodeURIComponent(_arrS[i].substring(pos + 1));
        _rs[name] = value;
    }
    return _rs;
}

//设置url参数
//setUrlPrmt({'a':1,'b':2})
//a=1&b=2
function setUrlPrmt(obj) {
    let _rs = [];
    for (let p in obj) {
        if (obj[p] != null && obj[p] != '') {
            _rs.push(p + '=' + obj[p])
        }
    }
    return _rs.join('&');
}

5-5随机返回一个范围的数字


function randomNumber(n1,n2){
    //randomNumber(5,10)
    //返回5-10的随机整数,包括5,10
    if(arguments.length===2){
        return Math.round(n1+Math.random()*(n2-n1));
    }
    //randomNumber(10)
    //返回0-10的随机整数,包括0,10
    else if(arguments.length===1){
        return Math.round(Math.random()*n1)
    }
    //randomNumber()
    //返回0-255的随机整数,包括0,255
    else{
        return Math.round(Math.random()*255)
    }  
}

5-6随进产生颜色

function randomColor(){
    //randomNumber是上面定义的函数
    //写法1
    return 'rgb(' + randomNumber(255) + ',' + randomNumber(255) + ',' + randomNumber(255) + ')';
    
    //写法2
    return '#'+Math.random().toString(16).substring(2).substr(0,6);
    
    //写法3
    var color='#';
    for(var i=0;i<6;i++){
        color+='0123456789abcdef'[randomNumber(15)];
    }
    return color;
}

//这种写法,偶尔会有问题。大家得注意哦
//Math.floor(Math.random()*0xffffff).toString(16);
clipboard.png

5-7Date日期时间部分

//到某一个时间的倒计时
//getEndTime('2017/7/22 16:0:0')
//"剩余时间6天 2小时 28 分钟20 秒"
function getEndTime(endTime){
    var startDate=new Date();  //开始时间,当前时间
    var endDate=new Date(endTime); //结束时间,需传入时间参数
    var t=endDate.getTime()-startDate.getTime();  //时间差的毫秒数
    var d=0,h=0,m=0,s=0;
    if(t>=0){
      d=Math.floor(t/1000/3600/24);
      h=Math.floor(t/1000/60/60%24);
      m=Math.floor(t/1000/60%60);
      s=Math.floor(t/1000%60);
    } 
    return "剩余时间"+d+"天 "+h+"小时 "+m+" 分钟"+s+" 秒";
}

5-8适配rem

这个适配的方法很多,我就写我自己用的方法。大家也可以去我回答过得一个问题那里看更详细的说明!移动端适配问题

function getFontSize(){
    var doc=document,win=window;
    var docEl = doc.documentElement,
    resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
    recalc = function () {
        var clientWidth = docEl.clientWidth;
        if (!clientWidth) return;
        //如果屏幕大于750(750是根据我效果图设置的,具体数值参考效果图),就设置clientWidth=750,防止font-size会超过100px
        if(clientWidth>750){clientWidth=750}
        //设置根元素font-size大小
        docEl.style.fontSize = 100 * (clientWidth / 750) + 'px';
    };
    //屏幕大小改变,或者横竖屏切换时,触发函数
    win.addEventListener(resizeEvt, recalc, false);
    //文档加载完成时,触发函数
    doc.addEventListener('DOMContentLoaded', recalc, false);
}
//使用方式很简单,比如效果图上,有张图片。宽高都是100px;
//样式写法就是
img{
    width:1rem;
    height:1rem;
}
//这样的设置,比如在屏幕宽度大于等于750px设备上,1rem=100px;图片显示就是宽高都是100px
//比如在iphone6(屏幕宽度:375)上,375/750*100=50px;就是1rem=50px;图片显示就是宽高都是50px;

6.封装成形

写了这么多的操作,小伙伴应该发现了一问题,全局函数太多了

//这样的话,封装了几个操作,就增加了几个全局函数,污染了全局变量,在开发中应该尽量避免全局变量。不说别的,如果一个项目,几个人开发,很有可能会造成命名的冲突。

function setUrlPrmt(obj){..}
function getUrlPrmt(url){..}
function upsetArr(obj){..}


//所以,建议的封装姿势是
var myJS={
    setUrlPrmt:function(obj){..},
    getUrlPrmt:function(url){..},
    upsetArr:function(arr){..},
}
这样就算别人也写这样的函数,也不会造成冲突。全局变量也只有一个,加上别人也不会很多!
var otherJS={
    setUrlPrmt:function(obj){..},
    getUrlPrmt:function(url){..},
    upsetArr:function(arr){..},
}


//最后,封装的效果是
var myJS={
    //去除字符串空格
    trim:function(obj){..},
    //字母大小写切换
    changeCase:function(obj){..},
    //字符串循环复制
    repeatStr:function(str){..},
    .....
}

//如果是es6的模块化开发,大家也可以
let myJS={
    //去除字符串空格
    trim:function(obj){..},
    //字母大小写切换
    changeCase:function(obj){..},
    //字符串循环复制
    repeatStr:function(str){..},
    .....
}
//暴露模块,里面的方式大家也可以用es6方式实现,代码至少会少一点!
export {myJS}
可能有小伙伴会有疑问,这样封装,调用有点麻烦,为什么不直接在原型上面封装,调用方便。比如下面的栗子!
String.prototype.trim=function(type){
    switch (type){
        case 1:return this.replace(/\s+/g,"");
        case 2:return this.replace(/(^\s*)|(\s*$)/g, "");
        case 3:return this.replace(/(^\s*)/g, "");
        case 4:return this.replace(/(\s*$)/g, "");
        default:return this;
    }
}
//'  12345 6 8 96  '.trim(1)
//"123456896"
//比这样trim('  12345 6 8 96  ',1)调用方便。
//但是,这样是不推荐的做法,这样就污染了原生对象String,别人创建的String也会被污染,造成不必要的开销。
//更可怕的是,万一自己命名的跟原生的方法重名了,就被覆盖原来的方法了
//String.prototype.substr=function(){console.log('asdasd')}  
//'asdasdwe46546'.substr()
//asdasd 
//substr方法有什么作用,大家应该知道,不知道的可以去w3c看下

所以在原生对象原型的修改很不推荐!至少很多的公司禁止这样操作!

7.小结

这篇文章,写了很久了,几个小时了,因为我写这篇文章,我也是重新改我以前代码的,因为我以前写的代码,功能一样,代码比较多,现在是边想边改边写,还要自己测试(之前的代码for循环很多,现在有很多简洁的写法代替)。加上最近公司比较忙,所以这一篇文章也是花了几天才整理完成。
源码都放在github上了,大家想以后以后有什么修改或者增加的,欢迎大家来star一下ec-do
我自己封装这个,并不是我有造轮子的习惯,而是:
1,都是一些常用,但是零散的小实例,网上基本没有插件。
2,因为零散的小实例,涉及到的有字符串,数组,对象等类型,就算找到插件,在项目引入的很有可能不止一个插件。
3.都是简单的代码,封装也不难。维护也简单。
废话就不多说了,上面的只是我自己在开发中常用到,希望能帮到小伙伴们,最理想就是这篇文章能起到一个抛砖引玉的作用,就是说,如果觉得还有什么操作是常用的,或者觉得我哪里写得不好的,也欢迎指出,让大家相互帮助,相互学习。

-------------------------华丽的分割线--------------------
想了解更多,关注关注我的微信公众号:守候书阁

clipboard.png

查看原文

赞 215 收藏 1220 评论 68

LP学长 收藏了文章 · 10月22日

个人分享--web前端学习资源分享

1.前言

6月份开始出没社区,现在差不多9月了,按照工作的说法,就是差不多过了三个月的试用期,准备转正了!一般来说,差不多到了转正的时候,会进行总结或者分享会议!那么今天我就把看过的一些学习资源(主要是博客,博文)推荐分享给大家。从我接触前端的开始,到发稿时间截止的这段时间我看过很多的博客,开源项目也了解过一些,质量有参差不齐(甚至还出现过广告文,鸡汤文)。但下面的推荐的博客,博文,项目或者其它资源都是我接触过的。不敢说资源最好,最全,适合每一个人,但是内容质量上让我满意的。可能有些资源我未必看完,看懂,看细。但我觉得是可以帮到大家的,能让大家提升技术水平的。希望能给大家起到一个引导,指路或者解惑的作用。如果大家有什么好的资源,也欢迎分享!

有一些资源链接,在后面我会加上括号的注释!比如:html5推广(介绍html5开发的创意广告) ,我加上注释的原因可能有以下几点:
1.资源很不错,干货比较多,提醒大家看!
2.资源是一个系列的其中一个资源。比如一个系列有20多篇文章,每一篇都有用,我不可能把20多篇链接都贴上,所以一般我贴这个系列的第一条,提醒大家不要忘记读完所在系列!
3.资源里面的内容有些我个人觉得很有用,有些我觉得没什么用!提醒大家挑着看,按需求看!
4.资源的标题可能会有些小误导,或者资源的其它注意事项,我加以自己的解释和备注。

2.推荐文章(教程)

html(5)

后HTML5时代I
后HTML5时代II(简单粗暴的介绍html5的api,没什么废话)
html5推广(介绍html5开发的创意广告)
匠心打造canvas签名组件
HTML5/CSS3(汤姆大叔总结的html5和css3的新特性)
H5动画开发快车道
移动H5前端性能优化指南
玩转HTML5移动页面
HTML5 进阶系列:文件上传下载
HTML5 进阶系列:canvas 动态图表
HTML5 进阶系列:拖放 API 实现拖放排序(这个系列的文章都不错,值得大家一看)
H5动效的常见制作手法
超多经典 canvas 实例,动态离子背景、移动炫彩小球、贪吃蛇、坦克大战、是男人就下100层、心形文字等等等
打造高大上的Canvas粒子动画

css(3)

CSS3属性教程与案例分享(大漠的CSS3干货,进去里面可以逐个看,对css3的学习很有用)
CSS3实现11种经典的CSS技术(大漠的CSS3经典实例干货,对css3的学习很有用)
W3cplus demo(w3cplus的案例,非常值得一看)
强大的css3(概括性的说明css3的新特性的一篇文章)
css3经典教程系列(不要忘记看完这个系列,文章都不错)
如何编写轻量级 CSS 框架
21条CSS高级技巧
原创:CSS3技术-雪碧图自适应缩放与精灵动画方案
Flex 布局教程:语法篇
Flex 布局教程:实例篇
奇妙的 CSS shapes(CSS图形)
使用CSS3制作各种形状的图形(转自others)
有趣的CSS题目(1): 左边竖条的实现方法(看了第一篇,可以往下面看,总会会帮助的,不过这个网站有时候加载不出图片,如果加载不出图片,放弃还是坚持,大家抓主意)
纯 CSS 实现波浪效果!
使用浏览器开发者工具检测CSS动画性能
搞定这些疑难杂症,向css3动画说yes
Effective前端1:能使用html/css解决的问题就不要使用JS
如何让你的动画更自然
css画图形
css各种鼠标效果
深入了解 CSS3 新特性(也是概括性的总结,这个系列值得推荐)
前端如何呼风唤雨
:after和:before炫酷用法总结
神奇的 conic-gradient 圆锥渐变
10 个优质的 CSS 与 JS LOGO 动画示例
被解放的GPU-提高CSS3性能
【项目总结】扯一扯电商网站前端css的整体架构设计(1)(看了第一篇,别忘了看下面的一个系列)
(以下几篇是我自己的文章,主要就是总结css3新特性,css3动画和过渡的实例,觉得对大家也会有帮助的 (^__^))
个人总结(css3新特性)
CSS3热身实战--过渡与动画(实现炫酷下拉,手风琴,无缝滚动)
编写自己的代码库(css3常用动画的实现)

javascript

JavaScript核心概念归纳整理(概括性的说明js的一些核心概念)
从达标到卓越 —— API 设计之道
我眼中的 JavaScript 函数式编程
深入理解JavaScript系列(汤姆大叔写的JavaScript系列,对学习和深入js很有用)
javascript组件化
如何写出小而清晰的函数?(JS 版)
javascript对象详解:__proto__和prototype的区别和联系
理清javascript中的面向对象(一)——原型继承(这个专栏(实用至上)文章质量不错,值得留意)
微前端-Javascript常用的设计模式详解
10 个你需要了解的最佳 javascript 开发实践
深入理解javascript原型和闭包系列
JavaScript学习总结(一)基础部分(这个系列的文章不错,不要忘记看完这个系列不过大家得在发文章的专栏找下这个系列的文章,因为作者在这个专栏发文不是连着发一个系列,中间也夹杂着其它文章,不过也很好找。在这专栏(trigkit4)里面,即使不是这个系列的文章,我看过其中几篇,感觉也很好,大家可以留意下)
JavaScript设计模式与开发实践 | 01 - 面向对象的JavaScript(这个系列的教程不错,《JavaScript设计模式与开发实践》的笔记,这本书我也看过,这本书不错,作者整理总结得也不错,大家看完,收获不少)
JavaScript 函数式编程存在性能问题么?
如何让 JS 写得更漂亮
JS的平凡之路--学习人气眼中的效果(上)
你还在用for循环大法麽?
前端模板的原理与实现
只有20行Javascript代码!手把手教你写一个页面模板引擎
【教学向】150行代码教你实现一个低配版的MVVM库(1)- 原理篇
【教学向】150行代码教你实现一个低配版的MVVM库(2)- 代码篇(可以关注这篇文章对应的专栏,写得不错,不知道作者还会不会写下一篇)
如何循序渐进、有效地学习JavaScript?
过目不忘JS正则表达式
JavaScript 进阶之深入理解数据双向绑定
利用 JavaScript 数据绑定实现一个简单的 MVVM 库
正则表达式30分钟入门教程
如何监听页面 DOM 变动并高效响应
IntersectionObserver API 使用教程(利用这个API可以更简单的实现图片懒加载和其它事情,性能也更高)
(下面三个连接,是知乎上的几个话题,里面的回答质量参差不齐,大家可以挑些高质量的回答看,绝对有帮助)
作为一名前端开发人员,有哪些值得一读的js代码?
JavaScript 有什么奇技淫巧?
用 JavaScript 可以做哪些有趣的事情?
(以下几篇是我自己的文章,前两篇主要是写js常用的一些小实例,虽然觉得跟很多大牛的文章比起来有差距,但是我觉得写得也不错,对大家也是有用,就贴上链接,最后一篇是关于我印象深刻的面试题)
编写自己的代码库(javascript常用实例的实现与封装)
编写自己的代码库(javascript常用实例的实现与封装--续)
让我印象深刻的javascript面试题

javascript(es6)

阮一峰 es6(可以当作es6文档用的在线书籍)
ES6 你可能不知道的事 - 基础篇
ES6 你可能不知道的事 - 进阶篇
30分钟掌握ES6/ES2015核心内容(简单粗暴介绍es6核心)
八段代码彻底掌握 Promise
ECMAScript 6 新特性(总结性描写常用的es6新特性)

jquery

jquery源码分析
jQuery中的100个技巧(译)(看了这篇文章,感觉自己学的是假jquery,不过很多技巧我没有亲身试过,保留意见)
这几条JQuery提升性能的规则你需要牢记
JQuery坑,说说哪些大家都踩过的坑(对于刚接触的新人而言,这篇文章还有帮助)
jQuery学习笔记(总结性说明jquery常用的api,也不错)
jQuery插件开发总结

vue

vue官网
Vue2.x踩坑与总结
Vue 中你不知道但却很实用的黑科技
一个栗子实践vue2.0与1.0的区别
我从未见过如此简洁易懂的Vue教程
vue-API(2.3版本的API大全)
Vue.js 插件开发详解
vue插件编写与实战(从开发到发布的一个流程,实例简单易懂)
使用 Vue.js 创建的 Calendar
加薪DAY10」Vue开源项目库汇总(杂七杂八的库都有,质量当然也是有高有低,大家挑着看。)
Vue.js 的实用技巧(一)(看了第一篇,别忘记剩下的)
深入vue2.0底层思想--模板渲染
Vue.js——vue-router 60分钟快速入门
Vue2.1.7源码学习
剖析Vue实现原理 - 如何实现双向绑定mvvm
Vue.js入门(一)--MVVM框架理解
Vue2 源码分析——逻辑梳理
vue快速入门的三个小实例(我自己的文章,比较基础的用法,觉得也还不错,就放上来了)

webpack

(下面的文章,有些是webpack1.x和2.x的,考虑到现在已经更新到3.x了,大家看时候要注意区别)
webpack中文网
webpack官方文档
webpack使用优化(基本篇)
webpack常用功能介绍
开发工具心得:如何 10 倍提高你的 Webpack 构建效率
Webpack 大法之 Code Splitting
webpack多页应用架构系列(一):一步一步解决架构痛点(看了第一篇不要忘记剩下的,这个系列的文章不错)

gulp

gulp详细入门教程
前端构建工具gulp的使用介绍及技巧
gulp入门指南

react

(react我刚接触不久,暂时看过的网站就这两个)
React中国
React 技术栈系列教程
React组件规范

git

猴子都能懂的GIT入门
Git教程 - 廖雪峰的官方网站

browsersync

(Browsersync中文网 - 省时的浏览器同步测试工具)

node.js

使用npm - NodeSource的绝对入门指南
Node.js和npm - NodeSource中的Package.json的基础知识
配置.npmrc以获取最佳Node.js环境

Markdown

认识与入门 Markdown

yoeman

教你从零开始搭建一款前端脚手架工具

lodash

这是一个具有一致接口、模块化、高性能等特性的 JavaScript 工具库。
4.17.4版本

Fiddler

官网
Fiddler教程

调试

分享几个日常调试方法让js调试更简单
用 Chrome 调试你的 JavaScript
JavaScript 中 console 的用法(看了这篇和上面一篇文章,你可能会感觉以前根本不会使用console)

细节,优化

浅谈移动前端的最佳实践
前端优化带来的思考,浅谈前端工程化
前端优化实践总结
减少前端代码耦合
判断单、多张图片加载完成
终端代码重复率检测
【组件化开发】前端进阶篇之如何编写可维护可升级的代码
作为一个web开发人员,哪些技术细节是在发布站点前你需要考虑到的
【单页应用】我们该如何处理框架弹出层层级关系?
剥离模板代码加速Web页面加载
移动前端系列——移动页面性能优化
[聊一聊系列]聊一聊百度移动端首页前端速度那些事儿

其它

socket.io 中文手册 socket.io 中文文档
WebSocket 与 Socket.IO
VR进化论|教你搭建通用的WebVR工程
RxJS 入门
deeplearn.js:浏览器端机器智能框架

分享和技能清单

鸡年大吉!继续前行的前端周刊(第十五期)
前端收藏夹(和上面那篇一样,都是别人的总结的,干货不少,但是‘前端收藏夹’,可能是资源太多了,给我的感觉也有点一股脑放资源的感觉,只要是资源,都放进去,少部分资源质量有点低,建议大家带参考思想看)
移动web干货收藏夹
awesomes(前端一个巨大的资源库,里面的资源应有尽有)
知识库(拥有很多语言,框架,库的知识图谱,值得一看!能让你了解哪个语言,框架,库所包含的知识点,每一个知识点的详情讲解,也有栗子和文章,如:javascript知识图谱html5知识图谱react知识图谱
前端资源分享
送给前端的你,推荐几篇前端汇总文章。 - 知乎专栏
前端面试问题合集(Front-end-Developer-Interview-Questions)
github上值得关注的前端项目
前端那点事儿(书列)
FPB 2.0:免费的计算机编程类中文书籍 2.0(迷渡大神分享的书籍,各类图书都有,也有前端的,大家可以挑着看)
腾讯 Web 工程师的前端书单
前端开发者手册
印记中文-手册(各种开发文档,webpack,vue,sass等)
(以下几个链接,不是文章,也不算问题吧,都是segmentfault官方举行的比赛,分享或者讨论的内容,在上面看大神的分享,代码,绝对有收获)
1024 HackGame 第四关面壁人这些题目是怎么想出来的?
把 SegmentFault 全部带回家 —— 码文章,送周边
官方送书活动第二弹 —— 增长姿势之SF喊你来谈开发
官方活动,深入浅出之SF喊你来谈开发!一起来分享一下你的开发知识?
官方30行js比赛:30行js你能做出什么?
【官方比赛】社区 1111 秀代码,让你来秀让你飞!

3.推荐博客

团队博客

淘宝前端团队fed
阿里巴巴
百度前端团队
360奇舞团
奇舞团视频教程
京东设计体验部-凹凸实验室
腾讯网前端研发中心
腾讯alloyteam
腾讯前端IMWEB团队
腾讯前端IMWEB团队-github
腾讯游戏
新浪UED
大前端(饿了么)
(上面的团队博客,值得关注,博文内容质量很高,但是就是更新不频繁,下面的更新会比较多,质量也不错)
搜车大无线团队博客
极乐科技
IMWeb前端社区
爱前端-知乎专栏
前端杂货铺
前端外刊评论
前端解忧杂货铺
DDFE 技术周刊
前端之巅
Fed汇总

大牛博客

十年踪迹
张鑫旭
阮一峰
叶小钗
司徒正美博客
廖雪峰
勾三股四
chokCoCo
chokCoCo-github

4.推荐书籍

我看过的书比较杂七杂八,与前端有关的,推荐的就是下面这些了!下面的书,我都看过,区别就在于有些我是整本都看完了(有些也来回看过几次),有些粗略的看了其中一部分(有些就看了几页)!

关于看书的建议,下面的书,大家可以根据自己的需求兴趣去挑着看。一本书,不要看完就可以了,要弄懂书中的内容。有些书值得大家来回看几遍的!

关于书的资源,下面的推荐的书(我看的是电子书)我都是从网上下载的。为避免侵权,我在这里就不放资源的链接了!大家自行购买纸质书或者电子书,或者到网上找资源!

JavaScript高级程序设计》:(红皮书或者红宝石书)可以作为入门书籍,但同时也是高级书籍,可以快速吸收基础,等到提升再回来重新看
JavaScript权威指南》:(犀牛书)可以说是js最经典的一本书了,新手看着可能会有点吃力,但是对于学js的人来说,必备的,不理解的地方就去查阅一下,很有帮助。
数据结构与算法JavaScript描述》(刺猬书)
编写可维护的JavaScript》(乌龟书)
JavaScript DOM编程艺术 第2版
JavaScript语言精粹》(蝴蝶书):经典的书,也是普及JavaScript的开发思维的一本书。
JavaScript设计模式 》-张榕铭(百度员工写的一本书,案例生动易懂,涉及的设计模式比较全)
JavaScript设计模式与开发实践》-曾琛(腾讯员工写的一本书,讲了js里面更加常用的14个设计模式)
单页Web应用 JavaScript从前端到后端
ECMAScript 6 入门》可以作为es6新特性文档的一本书,而且这本书开源了,直接看就好!需要的也可以购买!
HTML5移动Web开发实战
HTML5移动Web开发指南
HTML5 Canvas核心技术》:对于感受canvas的魅力,使用,动画的实现,都非常有帮助。
html5与css3权威指南 第三版》-陆凌牛,这本书貌似有同名的,注意作者,另外该书分上册和下册的。我当时看的是第二版,现在已经出了第三版就推荐第三版了。
锋利的jquery》:我学jquery就是看这本书,这本书给我身边的同行印象不是很好,但是我感觉不错,而且我学jquery就看了这一本书,就推荐这本了!
了不起的Node js 将JavaScript进行到底
css揭秘》:看了这本书,我再也不敢说自己会css了!里面很多都是你想不到的!

5.推荐社区

我推荐社区虽然有这么多个,但是我注册账号并且活跃的只有两三个,其它的社区基本都是我在学习的时候偶然接触到的,并且觉得不错的,所以也推荐下。至于大家想活跃哪几个社区,就看大家的选择了。如果大家有什么好的社区资源,也不妨推荐下!

github:这个起初我经常在上面找资源。但是没注册账号,但是后来被老大催着注册,就去了。在里面可以阅读别人的代码,向大家学习!
segmentfault:我现在活跃的一个社区,主打问答,也有文章和讲堂,质量也很不错!
知乎:虽然不是单纯的IT社区,但是里面有很多大牛和高质量的专栏!
stackoverflow:一个问答社区,基本上各种问题都能在上面获得解答
w3cplus:一个前端学习的网站,里面的文章质量都挺不错的,特别是关于CSS(3)的
前端网
前端乱炖:一个前端文章分享的社区,有很多优秀文章
开发者头条
前端周刊
HTML5梦工场
毕业僧
慕课网(貌似现在很多视频收费了,以前我看的时候,基本都是免费的)
稀土掘金
汇智网

6.公众号

前端大全

clipboard.png

segmentfault

clipboard.png

前端之巅

clipboard.png

前端早读课

clipboard.png

W3cplus

clipboard.png

稀土圈

clipboard.png

(以上图片来源网络)

7.推荐关注项目

github上的项目

vue
react(我只接触过几次react,使用起来还不错,现在也正在学习)
gulp
webpack
d3
chart(和d3一样,属于数据可视化方面,体验过。但是在项目上没用过,感觉功能强大,就推荐下)
element-ui(基于vue2.x开发的一个组件库,一般用于后台管理系统开发,现在我们公司开发的后台管理就是用这个)
muse-ui(同样是基于vue的组件库,ui非常的漂亮)
一起学 Node.js
AlloyTeam移动端手势解决方案
frontend-dev-bookmarks(前端各方面的学习清单)
免费的编程中文书籍(中文分享书籍,不止前端,各方面都有,大家按需查看)
animate.css
hover.css
muse-ui(基于vue2.0的一个UI库,样式漂亮,种类全)
CSS3 实现各类3D效果的博客(和下面三个连接同属一个作者,这个大牛的作品不错,效果酷炫,虽然star数量与其它几个推荐项目没法比,但是很值得推荐)
chokcoco/boomJS
chokcoco/magicCss
chokcoco/iCSS

梁少峰的个人博客(里面的文章质量也不错,各种都有,js,jquery,vue,react,node.js等)
RxJs
deeplearn(谷歌出版,一个关于人工智能深度学习的框架,有兴趣可以看,我看了一会,没看懂)

(下面这两个是我自己的开发的项目,虽然跟其他项目比较欠缺火候,但是还不至于用不了或者拿不出手的地步,也算是我的一个小广告吧!(●'◡'●))
ec-do(封装了日常开发常用的javascript实例,实例为日常开发常用的小实例,包括数组去重,打乱数组,字母大小写转换,cookie操作的封装等。)
ec-css(基于css3开发的代码库,包括日常使用的hover过渡效果,动画效果,以及预设动画)

其它推荐项目

腾讯css3 ui库
NEC : 更好的CSS样式解决方案(网易的一个css3解决方案,但是貌似挺旧了,不维护了,但是里面的案例大家可以参考,绝对有收获)

8.综合项目教程(学习实录)

使用node.js + socket.io + redis实现基本的聊天室场景
前端开发,从草根到英雄(第一部分)
前端开发,从草根到英雄(第二部分)
我的前端进阶之路(看着标题以为是杂谈,实际上感觉是一篇总结性文章,大家可以看下)
为你的移动页面寻找一丝新意——手机互动网页项目总结(上)
为你的移动页面寻找一丝新意——手机互动网页项目总结(下)
前端开发指南2017
【实战HTML5与CSS3 第一篇】初探水深,美丽的导航,绚丽的图片爆炸!!(看完别忘记下面的,是叶小钗大神仿智能社官网的实战)
基于 Vue 2.0 实现的移动端弹窗 (Alert, Confirm, Toast)组件.
发现最好的你-设计中如何打造最合适的组件
webpack+vue项目实战(一,搭建运行环境和相关配置)(我自己的文章,关于使用vue+webpack开发一个单页应用,一个后台管理系统的开发搭建,这个系列一共有五篇文章)

9.开发杂谈

OpenDoc - 前端简历评级标准(美团出的一份简历评级,可以适当参考下,对比下自己的实力和美团评级。当然有兴趣的也可以去面试)
前端代码规范及最佳实践
Vue.js 很好,但是比 Angular 或 React 更好吗?
聊聊技术选型 - Angular2 vs Vue2
写给前端工程师看的,移动应用选型指南
网站架构-从无到有
前端架构那些事儿
技术大牛养成指南,一篇不鸡汤的成功学实践
6 个值得好好学习的 JavaScript 框架
更快学习 JS 的 6 个简单思维技巧
程序员怎么学英语
8个让程序员追悔莫及的职业建议
当心!程序员应警惕七种错误的职业规划
1024程序员节,写给年轻程序员的建议
给新程序员的10条建议
前端框架这么多,该何去何从?
2017年前端开发工具趋势
现代前端开发技术栈
听说2017你想写前端?
如何跟上前端开发的最新前沿
前端开发人员必须了解的七大技能图谱
前端技能汇总(JacksonTian)
另一张前端技能汇总图(和上面的链接一样,但是看的话,建议大家带参考的思想看)
怎样成长为一个优秀的 Web 前端开发工程师?
月薪10-12k的前端人员应该具备怎样一种技术水平?
作为一名前端开发人员,有哪些值得一读的js代码?
hexo高阶教程:教你怎么让你的hexo博客在搜索引擎中排第一(有兴趣的可以看一下,有用)
开放源码的法律面(因为国外对版权,许可证很是注重,该文章就讲代码开源后关于许可证的文章,看得我有点蒙,但是最终整理后,我以后的开源项目就用MIT)

10.开发经常会接触的网站

MDN-web技术文档
智图_图片压缩在线工具_在线制作webp
多视通图片转换Base64
微信JS-SDK说明文档
草料二维码
菜鸟教程
w3cschool

11.小结

好了,我学前端看过的博客文章,关注过的大牛和项目,项目技术栈的教程基本都在这里了!我分享的这些,不敢说最好,最全,适合每个人。但是都是我看过或者接触过的,至少内容上让我满意的,让我觉得是对学习有用的,能帮到大家的。如果大家有什么学习资源推荐的,我也希望大家不吝啬的共享给我,给大家!
还有一个建议就是,学习web前端,上面分享的可以起到一个引导,指路,解惑的作用。但是,大家不要忘记多练,毕竟实操最重要!大家有空余的时间或者周末也可以自己做下自己有兴趣的项目,并且用在自己工作的项目开发上!项目不求比别人好,只求自己能练习,提升自己的技术水平!

-------------------------华丽的分割线--------------------
想了解更多,关注关注我的微信公众号:守候书阁

clipboard.png

查看原文

LP学长 收藏了文章 · 10月22日

25个JavaScript代码简写技巧(下篇)

14. 多行字符串

如果您发现自己需要在代码中编写多行字符串,那么您可以编写它:

常规:


但是有一种更简单的方法。只需使用反引号。

简写:

15. 展开(spread)操作符

ES6中引入的展开运算符有几个用例,可以使JavaScript代码更高效,更有趣。它可以用来替换某些数组函数。展开操作符只是连续的三个点。

常规:

简写:

与 concat() 函数不同,您可以使用spread运算符在另一个数组内的任何位置插入数组。

您还可以将spread运算符与ES6解构表示法结合使用:

16. 强制参数

默认情况下,如果未传递值,JavaScript会将函数参数设置为undefined。其他一些语言会引发警告或错误。要强制执行参数赋值,可以使用 if 语句在未定义时抛出错误。

常规:

简写:

17. Array.find

如果您曾经在纯JavaScript中编写过 find 函数,那么您可能已经使用了for循环。在ES6中,引入了一个名为 find() 的新数组函数

常规:

简写:

18. Object[key]

你知道Foo.bar也可以写成Foo ['bar']吗?起初,似乎没有理由应该这样写。但是,这种表示法可以为编写可重用代码块提供方便。

思考一下,验证函数的这个简化示例:

这个功能完美地完成了它的工作,但是请考虑这样一种情况,即您需要验证的表单有很多,表单都具有不同字段和规则。构建可在运行时配置的通用验证函数不是更好吗?

简写:


现在我们有了一个通用验证函数,可以在所有表​​单中重用,而无需为每个表单编写自定义验证函数。

19. 双取反运算

双取反运算符有一个非常实用的场景。您可以将它用作 Math.floor() 的替代品。 双取反运算符的优点是它可以更快地执行相同的操作。

常规:

简写:

20. 数学指数幂函数

常规:

简写:

图片描述

21. 将字符串转换为数字

有时,您的代码会接收String类型的参数,但需要以数字类型处理。这不是什么大问题,我们可以进行快速转换。

常规:

简写:

22. 对象属性赋值

考虑以下代码:

你会如何把它们合并为一个对象?

一种方法是编写一个将第二个对象的数据复制到第一个对象的函数。

最简单的方法是,使用ES6中引入的 Object.assign 函数:

您还可以使用ES8中引入的对象销毁表示法:

您可以合并的对象属性数量没有限制。如果确实具有相同属性名称的对象,则将按合并的顺序覆盖值。

23. 取反运算 和 IndexOf

使用数组执行查找时,indexOf() 函数用于检索您要查找的项目的位置。如果未找到该项,则返回值-1。在JavaScript中,0被认为是'falsy',而大于或小于0的数字被认为是'truthy'。因此,必须像这样编写正确的代码。

常规:

简写:

取反(〜)运算符对 -1 以外的任何值,都返回 truthy 值。对它进行非运算,直接 !〜。

或者,我们也可以使用 includes() 函数:

24. Object.entries()

这是ES8中引入的一项功能,允许您将对象转换为键/值对数组。请参阅以下示例:

25. Object.values()

这也是ES8中引入的一个新功能,它执行与 Object.entries() 类似的功能,但没有key部分:

图片描述

看到这里,都已经介绍完了。才疏学浅,如果您有其他好的技巧,欢迎留言评论。


相关文章:

25个JavaScript代码简写技巧(上篇)

使用Array.isArray更好地检查数组

Vue.js应用性能优化一

Vue.js应用性能优化二

Vue.js应用性能优化三

如何使用Vue.js渲染JSON中定义的动态组件

构建Vue.js组件的10个技巧

查看原文

LP学长 收藏了文章 · 10月22日

25个JavaScript代码简写技巧(上篇)


对于任何JavaScript开发人员来说,这篇文章很值得一读。这里记录了我多年来学习的JavaScript代码简洁写法,也给大家提供一些编码上的思考和取舍。

1. 三元(三目)运算符

如果只想在一行中编写if..else语句时,这是一个很好的节省代码的方式。

常规:

简写:

嵌套版三元运算:

2. 短路判断简写

将变量值分配给另一个变量时,您可能希望确保源变量不为null,undefined或为空。您可以编写带有多个条件的长 if 语句,也可以使用短路判断。

常规:

简写:

再来点示例,尝试一下:

请注意,如果将variable1设置为false或0,则赋值为bar。

3. 声明变量简写

常规:

简写:

4. If真值判断简写

这可能是微不足道的,但值得一提。在执行“if 检查”时,有时可以省略全等运算符。

常规:

简写:

注意:这两个例子并不完全相同,因为只要likeJavaScript是一个真值,检查就会通过。

这是另一个例子。如果a不等于true,那就做点什么吧。

常规:

简写:

5. For循环简写

如果您想要纯JavaScript并且不想依赖外部库(如jQuery或lodash),这个小技巧非常有用。

常规:

简写:

如果您只想访问索引,请执行以下操作:

如果要访问文字对象中的键,这也适用:

Array.forEach简写:

6. 短路判断赋值

如果预期参数为null或undefined,我们可以简单地使用短路逻辑运算符,只需一行代码即可完成相同的操作,而不是编写六行代码来分配默认值。

常规:

简写:

7. 十进制基本指数

你可能已经看过这个了。它本质上是一种编写没有尾随零的数字的奇特方式。例如,1e7实质上意味着1后跟7个零。它表示一个十进制基数(JavaScript解释为浮点类型)等于10,000,000。

常规:

简写:

8. 对象属性简写

在JavaScript中定义对象很简单。 ES6提供了一种更简单的方法来为对象分配属性。如果变量名称与对象键相同,则可以使用简写表示法。

常规:

简写:

9. 箭头函数简写

经典函数以简单的形式易于读写,但是一旦你开始将它们嵌套在其他函数调用中,它们往往会变得有点冗长和混乱。

常规:

简写:

重要的是要注意,箭头函数内部的 this 与常规函数的不同,因此这两个示例并不严格等效。有关详细信息,请参阅有关箭头函数语法的文章。

10. 隐式返回简写

Return 是我们经常使用的关键字,用于返回函数的最终结果。具有单个语句的箭头函数将隐式返回其执行结果(函数必须省略大括号({})以省略return关键字)。

要返回多行语句(例如对象),必须使用 () 而不是 {} 来包装函数体。这可确保将代码执行为单个语句。

常规:

简写:

11. 默认参数值

您可以使用if语句定义函数参数的默认值。在ES6中,您可以在函数声明本身中定义默认值。

常规:

简写:

12. 模板字符串

您是否厌倦了使用 '+' 将多个变量连接成一个字符串?有没有更简单的方法?如果你能够使用ES6,那么你很幸运。您需要做的就是使用反引号,并使用 ${} 来包含变量。

常规:

简写:

13. 解构赋值简写

如果您正在使用任何流行的Web框架,那么很有可能您将使用对象形式的数组或数据,在组件和API之间传递信息。数据对象到达组件后,您需要将其解构。

常规:

简写:

您甚至可以分配自己的变量名称,比如entity替换原来对象中的contact:


相关文章:

25个JavaScript代码简写技巧(下篇)

使用Array.isArray更好地检查数组

Vue.js应用性能优化一

Vue.js应用性能优化二

Vue.js应用性能优化三

如何使用Vue.js渲染JSON中定义的动态组件

构建Vue.js组件的10个技巧

查看原文

LP学长 赞了文章 · 10月22日

CSS backgroundImage 7个好用的技巧

作者:ryanmcdermott
译者:前端小智
来源:github

今天送 5 本前端的书,后天开奖,抽奖地址点击这里https://mp.weixin.qq.com/s/nb...,祝大家好运。

点赞再看,微信搜索 【大迁世界】 关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

背景图像可能是我们所有前端开发人员在我们的职业生涯中至少使用过几次的CSS属性之一。大多数人认为背景图像不可能有任何不寻常的地方,但经过研究,答案并非如此。所以本文收集了七个我认为最有用的技巧,并创建了一些代码示例。

1.背景图如何才能完美适配视口

让背景图适配视口很容易,需要使用下面 CSS 即可:

body {
  background-image: url('https://images.unsplash.com/photo-1573480813647-552e9b7b5394?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2253&q=80');
  background-repeat: no-repeat;
  background-position: center;
  background-attachment: fixed;
  background-size: cover;
  -webkit-background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
}

clipboard.png

事例源码:https://codepen.io/duomly/pen...

2.如何在CSS中使用多个背景图片?

如果我想在背景中添加一张以上的图片怎么办?CSS3 中可以直接 指定多个背景路径,如下所示:

body {
  background-image: url(https://image.flaticon.com/icons/svg/748/748122.svg), url(https://images.unsplash.com/photo-1478719059408-592965723cbc?ixlib=rb-1.2.1&auto=format&fit=crop&w=2212&q=80);
  background-position: center, top;
  background-repeat: repeat, no-repeat;
  background-size: contain, cover;
}

clipboard.png

事例源码:https://codepen.io/duomly/pen...

3.如何创建一个三角形的背景图像

另一个很酷的背景特效就是三角形背景,当我们想展示某些完全不同的选择(例如白天和黑夜或冬天和夏天)时,这种特效就更加棒。

思路是这样的,首先创建两个div,然后将两个背景都添加到其中,然后,第二个div使用clip-path属性画出三角形。

clipboard.png

html

<body>
  <div class="day"></div>
  <div class="night"></div>
</body>

css

body {
  margin: 0;
  padding: 0;
}

div {
  position: absolute;
  height: 100vh;
  width: 100vw;
}

.day {
  background-image: url("https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2613&q=80");
  background-size: cover;
  background-repeat: no-repeat;
}

.night {
  background-image: url("https://images.unsplash.com/photo-1493540447904-49763eecf55f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  background-size: cover;
  background-repeat: no-repeat;
  clip-path: polygon(100vw 0, 0% 0vh, 100vw 100vh);
}

源码:https://codepen.io/duomly/pen...

4.如何在背景图像上添加叠加渐变?

有时我们想在背景上添加一些文字,但有的图片太亮,导致字看不清楚,所以这里我们就需要让背景图叠加一些暗乐来突出文字效果。

例如,可以通过添加粉红橙色渐变或红色至透明渐变来增强日落图像,这些情况下使用叠加的渐变就很容易做到。

clipboard.png

css

body {
  background-image: 
    linear-gradient(4deg, rgba(38,8,31,0.75) 30%, rgba(213,49,127,0.3) 45%, rgba(232,120,12,0.3) 100%),
    url("https://images.unsplash.com/photo-1503803548695-c2a7b4a5b875?ixlib=rb-1.2.1&auto=format&fit=crop&w=2250&q=80");
  background-size: cover;
  background-repeat: no-repeat;
  background-attachment: fixed;
  background-position: center
}

源码:https://codepen.io/duomly/pen...

5.如何创建一个颜色动态变化的背景

如果你很多颜色,你想确认哪种颜色更适合背景图片的颜色,刚动态更改背景颜色的技巧就很有用。

css

HTML CSSResult
EDIT ON
@keyframes background-overlay-animation {
  0%   {
      background-image: 
        linear-gradient(4deg, rgba(255,78,36,0.3) 50%, rgba(255,78,36,0.3) 100%), url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  25%  {
      background-image: 
         linear-gradient(4deg, rgba(213,49,127,0.3) 50%, rgba(213,49,127,0.3) 100%), url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  50%  {
    background-image: 
       linear-gradient(4deg, rgba(36,182,255,0.3) 50%, rgba(36,182,255,1) 100%),
     url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  100% {
    background-image: 
        linear-gradient(4deg, rgba(0,255,254,0.3) 50%, rgba(0,255,254,0.3) 100%),
        url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
}

@-webkit-keyframes background-overlay-animation {
  0%   {
      background-image: 
        linear-gradient(4deg, rgba(255,78,36,0.3) 50%, rgba(255,78,36,0.3) 100%)
        url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  25%  {
      background-image: 
         linear-gradient(4deg, rgba(213,49,127,0.3) 50%, rgba(213,49,127,0.3) 100%),
        url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  50%  {
    background-image: 
       linear-gradient(4deg, rgba(36,182,255,0.3) 50%, rgba(36,182,255,1) 100%),
     url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  100% {
    background-image: 
        linear-gradient(4deg, rgba(0,255,254,0.3) 50%, rgba(0,255,254,0.3) 100%),
  

图片描述

源码:https://codepen.io/duomly/pen...

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

6. 如何制作网格背景图像?

有时候会遇到一些需要有艺术或者摄影类的项目,他们一般要求网站要有艺术信息,要有创意。网络的背景就挺有创意的,效果如下:

clipboard.png

HTML

<body>
<div class="container">
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
</div>
</body>

scss

body {
 margin: 0;
  padding: 0;
}

.container {
  position: absolute;
  width: 100%;
  height: 100%;
  background: black;
  display: grid;
  grid-template-columns: 25fr 30fr 40fr 15fr;
  grid-template-rows: 20fr 45fr 5fr 30fr;
  grid-gap: 20px;
  .item_img {
    background-image: url('https://images.unsplash.com/photo-1499856871958-5b9627545d1a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2207&q=80');
  background-repeat: no-repeat;
  background-position: center;
  background-attachment: fixed;
  background-size: cover;
}
}

源码:https://codepen.io/duomly/pen...

7.如何将背景图像设置为文本颜色?

使用background-image background-clip ,可以实现背景图像对文字的优美效果。 在某些情况下,它可能非常有用,尤其是当我们想创建一个较大的文本标题而又不如普通颜色那么枯燥的情况。

图片描述

HTML

<body>
  <h1>Hello world!</h1>
</body>

SCSS

body {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  width: 100%;
  text-align: center;
  min-height: 100vh;
  font-size: 120px;
  font-family:Arial, Helvetica, sans-serif;
}

h1 {
   background-image: url("https://images.unsplash.com/photo-1462275646964-a0e3386b89fa?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2600&q=80");
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}

源码:https://codepen.io/duomly/pen...


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://dev.to/duomly/discove...


交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 35 收藏 28 评论 8

LP学长 收藏了文章 · 10月22日

CSS backgroundImage 7个好用的技巧

作者:ryanmcdermott
译者:前端小智
来源:github

今天送 5 本前端的书,后天开奖,抽奖地址点击这里https://mp.weixin.qq.com/s/nb...,祝大家好运。

点赞再看,微信搜索 【大迁世界】 关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

背景图像可能是我们所有前端开发人员在我们的职业生涯中至少使用过几次的CSS属性之一。大多数人认为背景图像不可能有任何不寻常的地方,但经过研究,答案并非如此。所以本文收集了七个我认为最有用的技巧,并创建了一些代码示例。

1.背景图如何才能完美适配视口

让背景图适配视口很容易,需要使用下面 CSS 即可:

body {
  background-image: url('https://images.unsplash.com/photo-1573480813647-552e9b7b5394?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2253&q=80');
  background-repeat: no-repeat;
  background-position: center;
  background-attachment: fixed;
  background-size: cover;
  -webkit-background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
}

clipboard.png

事例源码:https://codepen.io/duomly/pen...

2.如何在CSS中使用多个背景图片?

如果我想在背景中添加一张以上的图片怎么办?CSS3 中可以直接 指定多个背景路径,如下所示:

body {
  background-image: url(https://image.flaticon.com/icons/svg/748/748122.svg), url(https://images.unsplash.com/photo-1478719059408-592965723cbc?ixlib=rb-1.2.1&auto=format&fit=crop&w=2212&q=80);
  background-position: center, top;
  background-repeat: repeat, no-repeat;
  background-size: contain, cover;
}

clipboard.png

事例源码:https://codepen.io/duomly/pen...

3.如何创建一个三角形的背景图像

另一个很酷的背景特效就是三角形背景,当我们想展示某些完全不同的选择(例如白天和黑夜或冬天和夏天)时,这种特效就更加棒。

思路是这样的,首先创建两个div,然后将两个背景都添加到其中,然后,第二个div使用clip-path属性画出三角形。

clipboard.png

html

<body>
  <div class="day"></div>
  <div class="night"></div>
</body>

css

body {
  margin: 0;
  padding: 0;
}

div {
  position: absolute;
  height: 100vh;
  width: 100vw;
}

.day {
  background-image: url("https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2613&q=80");
  background-size: cover;
  background-repeat: no-repeat;
}

.night {
  background-image: url("https://images.unsplash.com/photo-1493540447904-49763eecf55f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  background-size: cover;
  background-repeat: no-repeat;
  clip-path: polygon(100vw 0, 0% 0vh, 100vw 100vh);
}

源码:https://codepen.io/duomly/pen...

4.如何在背景图像上添加叠加渐变?

有时我们想在背景上添加一些文字,但有的图片太亮,导致字看不清楚,所以这里我们就需要让背景图叠加一些暗乐来突出文字效果。

例如,可以通过添加粉红橙色渐变或红色至透明渐变来增强日落图像,这些情况下使用叠加的渐变就很容易做到。

clipboard.png

css

body {
  background-image: 
    linear-gradient(4deg, rgba(38,8,31,0.75) 30%, rgba(213,49,127,0.3) 45%, rgba(232,120,12,0.3) 100%),
    url("https://images.unsplash.com/photo-1503803548695-c2a7b4a5b875?ixlib=rb-1.2.1&auto=format&fit=crop&w=2250&q=80");
  background-size: cover;
  background-repeat: no-repeat;
  background-attachment: fixed;
  background-position: center
}

源码:https://codepen.io/duomly/pen...

5.如何创建一个颜色动态变化的背景

如果你很多颜色,你想确认哪种颜色更适合背景图片的颜色,刚动态更改背景颜色的技巧就很有用。

css

HTML CSSResult
EDIT ON
@keyframes background-overlay-animation {
  0%   {
      background-image: 
        linear-gradient(4deg, rgba(255,78,36,0.3) 50%, rgba(255,78,36,0.3) 100%), url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  25%  {
      background-image: 
         linear-gradient(4deg, rgba(213,49,127,0.3) 50%, rgba(213,49,127,0.3) 100%), url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  50%  {
    background-image: 
       linear-gradient(4deg, rgba(36,182,255,0.3) 50%, rgba(36,182,255,1) 100%),
     url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  100% {
    background-image: 
        linear-gradient(4deg, rgba(0,255,254,0.3) 50%, rgba(0,255,254,0.3) 100%),
        url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
}

@-webkit-keyframes background-overlay-animation {
  0%   {
      background-image: 
        linear-gradient(4deg, rgba(255,78,36,0.3) 50%, rgba(255,78,36,0.3) 100%)
        url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  25%  {
      background-image: 
         linear-gradient(4deg, rgba(213,49,127,0.3) 50%, rgba(213,49,127,0.3) 100%),
        url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  50%  {
    background-image: 
       linear-gradient(4deg, rgba(36,182,255,0.3) 50%, rgba(36,182,255,1) 100%),
     url("https://images.unsplash.com/photo-1559310589-2673bfe16970?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80");
  }
  100% {
    background-image: 
        linear-gradient(4deg, rgba(0,255,254,0.3) 50%, rgba(0,255,254,0.3) 100%),
  

图片描述

源码:https://codepen.io/duomly/pen...

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

6. 如何制作网格背景图像?

有时候会遇到一些需要有艺术或者摄影类的项目,他们一般要求网站要有艺术信息,要有创意。网络的背景就挺有创意的,效果如下:

clipboard.png

HTML

<body>
<div class="container">
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
  <div class="item_img"></div>
  <div class="item"></div>
</div>
</body>

scss

body {
 margin: 0;
  padding: 0;
}

.container {
  position: absolute;
  width: 100%;
  height: 100%;
  background: black;
  display: grid;
  grid-template-columns: 25fr 30fr 40fr 15fr;
  grid-template-rows: 20fr 45fr 5fr 30fr;
  grid-gap: 20px;
  .item_img {
    background-image: url('https://images.unsplash.com/photo-1499856871958-5b9627545d1a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2207&q=80');
  background-repeat: no-repeat;
  background-position: center;
  background-attachment: fixed;
  background-size: cover;
}
}

源码:https://codepen.io/duomly/pen...

7.如何将背景图像设置为文本颜色?

使用background-image background-clip ,可以实现背景图像对文字的优美效果。 在某些情况下,它可能非常有用,尤其是当我们想创建一个较大的文本标题而又不如普通颜色那么枯燥的情况。

图片描述

HTML

<body>
  <h1>Hello world!</h1>
</body>

SCSS

body {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  width: 100%;
  text-align: center;
  min-height: 100vh;
  font-size: 120px;
  font-family:Arial, Helvetica, sans-serif;
}

h1 {
   background-image: url("https://images.unsplash.com/photo-1462275646964-a0e3386b89fa?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2600&q=80");
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}

源码:https://codepen.io/duomly/pen...


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://dev.to/duomly/discove...


交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

LP学长 发布了文章 · 8月18日

Mybatis 入门

学习来源:Bilibili 遇见狂神说 Mybatis最新完整教程IDEA版通俗易懂

环境搭建

Maven模块导入

pom.xml

<!-- mysql-connector-java -->
<!-- mybatis -->
<!-- junit -->

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

配置文件

mybatis-config.xml

<configuration>
    <!-- 加载类路径下的属性文件 -->
    <properties resource="db.properties"/>

    <!-- 默认连接环境配置 -->
    <environments default="mysql_developer">
        <!-- 连接环境信息 -->
        <environment id="mysql_developer">
            <!-- mybatis使用jdbc事务管理方式 -->
            <transactionManager type="jdbc"/>
            <!-- 使用连接池的方式来获取连接 -->
            <dataSource type="pooled">
                <!-- 配置与数据库交互的4个必要属性 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3307/lpxz_blog?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Hongkong" />
                <property name="username" value="test" />
                <property name="password" value="1234" />
            </dataSource>
        </environment>
    </environments>
</configuration>

编写 Mybatis 工具类获取 sqlSession

MybatisUtil.java

/* 加载 mybatis-config.xml 配置文件 */
static {
    try {
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    } catch (IOException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

/**
     * 获取SqlSession
     */
public static SqlSession getSqlSession() {
    // 从当前线程中获取 SqlSession 对象
    SqlSession sqlSession = threadLocal.get();
    // 如果 SqlSession 对象为空
    if (sqlSession == null) {
        // 在SqlSessionFactory 非空的情况下,获取 SqlSession 对象
        sqlSession = sqlSessionFactory.openSession();
        // 将 SqlSession 对象与当前线程绑定在⼀起
        threadLocal.set(sqlSession);
    }
    // 返回 SqlSession 对象
    return sqlSession;
}

SqlSessionFactoryBuilder

​ 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

​ SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

​ 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

​ 在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。

Dao 层

UserInfoDao.java

public interface UserInfoDao {
    List<UserInfo> getUserInfoList();
}

接口实现类

UserInfoMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace=绑定一个对应的 Dao/Mapper 接口 -->
<mapper namespace="com.lpxz.lpxzblog.dao.UserInfoDao">
    <select id="getUserInfoList" resultType="com.lpxz.lpxzblog.entity.UserInfo">
        select * from user_info
    </select>
</mapper>

测试类

UserInfoDaoTest.java

@Test
public void findUserInfoById() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserInfoDao dao = sqlSession.getMapper(UserInfoDao.class);

    UserInfo userInfo = dao.getUserInfoById(1); // 参数为 id
    System.out.println(userInfo);

    // 提交并关闭 SqlSession
    sqlSession.commit();
    sqlSession.close();
}

@Test
public void getUserInfoList() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserInfoDao dao = sqlSession.getMapper(UserInfoDao.class);

    List<UserInfo> userInfoList = dao.getUserInfoList();
    System.out.println(userInfoList);

    // 提交并关闭 SqlSession
    sqlSession.commit();
    sqlSession.close();
}

CRUD (Create, Retrieve, Update, Delete)

namespace

指定了类路径下的 Dao/Mapper文件

select, insert, update, delete

==id== 对应的 namespace 中的方法名

==resultType== sql语句执行的返回值类型

==parameterType== 参数类型

Map 传递参数

模糊查询(like “%%”)

Configuration

类型别名(typeAliases)

方法一可以自定义别名

<typeAliases>
    <typeAlias type="com.lpxz.lpxzblog.entity.UserInfo" alias="UserInfo"/>
</typeAliases>

方法二通过注解实现别名:@Alias(“${definedName}”)

<typeAliases>
    <package name="com.lpxz.lpxzblog.entity"/>
</typeAliases>

resultMap

结果集映射

UserInfo.java

@Data
@TableName("user_info") // @TableName中的值对应着表名
public class UserInfo {
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer password; // 将 pwd 改为 passsword
}

UserInfoMapper.xml

<select id="getUserInfoList" resultMap="userMap">
    select * from user_info
</select>

<!-- 结果集映射 -->
<resultMap id="userMap" type="userInfo">
    <!-- column->数据库中的字段 property->实体类中的属性 --> 
    <result column="id" property="id"></result>
    <result column="name" property="name"></result>
    <result column="pwd" property="password"></result>
</resultMap>

复杂的使用还没有讲

日志工厂

如果一个数据库操作出现了异常,我们需要排错,日志是最好的助手。

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

Log4j

通过修改配置文件,控制每一条日志的输出格式,定义每一条日志信息的级别,不需要修改应用的代码。

日志可以输出到控制台、文件、GUI 组件

mybatis-config.xml

<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>

pom.xml

<!-- 日志 Log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>${log4j-version}</version>
</dependency>

log4j.properties

# 日志等级
log4j.rootLogger=DEBUG,console,file

# 控制台输出
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
#log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

# 文件输出
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/lpxzLog.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

# 日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

使用

logger.info("info");
logger.debug("debug");
logger.error("error!");

分页

为什么要分页?

  • 减少数据的处理量

Limit 分页

SELECT * from user limit {startIndex}, {pageSize}; -- 开始位置和页面容量
SELECT * from user limit {n}; -- [0, n]
<mapper>
    <!-- 分页 -->
    <select id="getUserByLimit" parameterType="map" resultType="UserInfo">
        select * from user_info limit #{startIndex}, #{pageSize}
    </select>
</mapper>

UserInfoMapperTest.java

/**
 * 分页
 */
@Test
public void getUserInfoByLimit() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);

    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("startIndex", 0);
    map.put("pageSize", 2);

    List<UserInfo> userInfoList = mapper.getUserByLimit(map);
    for (UserInfo userInfo : userInfoList) {
        System.out.println(userInfo);
    }

    sqlSession.close();
}

RouBounds 分页

面向对象方法实现分页,仅作了解

使用注解开发

==AOP 面向接口编程思想==

UserInfoMapper.java

// 查询全部用户信息 注解方式
@Select("select * from user_info")
List<UserInfo> getUserInfoListAOP();

mybatis-config.xml

<mappers>
    <!-- 绑定接口 -->
    <mapper class="com.lpxz.lpxzblog.dao.UserInfoMapper"/>
</mappers>
关于 @Param

@Param 注解用于给方法参数起一个名字。以下是总结的使用原则:

  • 在方法只接受一个参数的情况下,可以不使用 @Param。
  • 在方法接受多个参数的情况下,建议一定要使用 @Param注解给参数命名。
  • 如果参数是 JavaBean , 则不能使用 @Param。
  • 不使用 @Param 注解时,参数只能有一个,并且是 Java Bean。
# 与 $ 的区别
  • #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】

    INSERT INTO user (name) VALUES (#{name});
    INSERT INTO user (name) VALUES (?);
  • ${} 的作用是直接进行字符串替换

    INSERT INTO user (name) VALUES ('${name}');
    INSERT INTO user (name) VALUES ('kuangshen');

Lombok

==@Data== ==@AllArgsConstructor== ==@NoArgsConstructor==

...

多对一的处理

多对一的理解:

  • 多个学生对应一个老师

搭建测试环境

==创建实体类==

@Data 
//GET,SET,ToString,有参,无参构造
public class Teacher {
    private int id;
    private String name;
}

@Data
public class Student {
    private int id;
    private String name;
    //多个学生可以是同一个老师,即多对一
    private Teacher teacher;
}

==编写实体类对应的Mapper接口==

public interface StudentMapper {
}
public interface TeacherMapper {
}

==编写Mapper接口对应的 mapper.xml==

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.StudentMapper"/>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.TeacherMapper"/>

按查询嵌套处理

StudentMapper.java

// 获取所有学生及对应老师的信息
List<Student> getStudents();

StudentMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.StudentMapper">
    <select id="getStudents" resultMap="StudentTeacher">
        select * from student
    </select>

    <resultMap id="StudentTeacher" type="Student">
        <!-- 对象:association     集合:collection -->
        <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacherById"/>
    </resultMap>

    <select id="getTeacherById" resultType="Teacher">
        select * from teacher where id = #{id}
    </select>
</mapper>

StudentMapperTest.java

@Test
public void testGetStudents() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

    List<Student> students = mapper.getStudents();

    for (Student student : students) {
        System.out.println(student);
    }

    sqlSession.commit();
    sqlSession.close();
}

按结果嵌套处理

StudentMapper.java

List<Student> getStudents2();

StudentMapper.xml

<select id="getStudents2" resultMap="StudentTeacher2" >
    select s.id sid, s.name sname , t.name tname
    from student s,teacher t
    where s.tid = t.id
</select>

<resultMap id="StudentTeacher2" type="Student">
    <id property="id" column="sid"/>
    <result property="name" column="sname"/>
    <!--关联对象property 关联对象在Student实体类中的属性-->
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
    </association>
</resultMap>

StudentMapperTest.java

@Test
public void testGetStudents2() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

    List<Student> students = mapper.getStudents2();

    for (Student student : students) {
        System.out.println(
            "学生名:" + student.getName()
            + "\t老师:" + student.getTeacher().getName());
    }
}

按照查询进行嵌套处理就像SQL中的子查询

按照结果进行嵌套处理就像SQL中的联表查询

一对多的处理

==修改实体类==

@Data
public class Teacher {
    private int id;
    private String name;
    private List<Student> studentList;
}

@Data
public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

按照结果嵌套处理

TeacherMapper.java

Teacher getTeacherById(@Param("tId") int id);

TeacherMapper.xml

<select id="getTeacherById" resultMap="TeacherStudent">
    select s.id sId, s.name sName, t.name tName, t.id tId
    from student s, teacher t
    where s.tId = t.id and t.id = #{tId}
</select>
<resultMap id="TeacherStudent" type="Teacher">
    <result property="id" column="tId"/>
    <result property="name" column="tName"/>
    <!-- 集合:collection
        javaType="" 指定属性的类型
        集合中的泛型信息,使用 ofType 获取
        -->
    <collection property="studentList" ofType="Student">
        <result property="id" column="sId"/>
        <result property="name" column="sName"/>
        <result property="tId" column="tId"/>
    </collection>
</resultMap>

TeacherMapperTest.java

@Test
public void testGetTeacherById() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);

    Teacher teacher = mapper.getTeacherById(1);

    System.out.println(teacher);

    sqlSession.commit();
    sqlSession.close();
}

按照查询嵌套处理

TeacherMapper.java

Teacher getTeacherById2(@Param("tId") int id);

TeacherMapper.xml

<select id="getTeacherById2" resultMap="TeacherStudent2">
    select * from teacher where id = #{tId}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
    <collection property="studentList" ofType="Student" select="getStudentByTeacher" column="id"/>
</resultMap>
<select id="getStudentByTeacher" resultType="Student">
    select * from student where tId = #{tId}
</select>

TeacherMapperTest.java

@Test
public void testGetTeacherById2() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);

    Teacher teacher = mapper.getTeacherById2(1);

    System.out.println(teacher);

    sqlSession.commit();
    sqlSession.close();
}

关联 - association 多对一

集合 - collection 一对多

javaType 用来指定实体类中属性的类型

ofType 用来指定映射到 List 或者集合中的 POJO 类型,泛型中的约束类型

动态 SQL

动态 SQL:指根据不同的条件生成不同的 SQL 语句

==if==

*Mapper.xml

<select id="queryBlogIf" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

==choose==

*Mapper.xml

<select id="queryBlogChoose" resultType="Blog">
    select * from blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            </when>
            <when test="author != null">
                and title = #{author}
            </when>
            <otherwise>
                and views = #{views}
            </otherwise>
        </choose>
    </where>
</select>
第一个之后的 when, otherwise 标签中的语句前不加 and 会报错

==set==

BlogMapper.xml

<update id="updateBlog" parameterType="Blog">
    update blog
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author}
        </if>
    </set>
    where id = #{id}
</update>
if 标签中语句末不加逗号会报错

==SQL 片段==

有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

提取 SQL 片段:

<sql id="if-title-author">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>

引用 SQL 片段:

<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <include refid="if-title-author"/>
    </where>
</select>

==Foreach==

将数据库中前三个数据的 id 修改为 1,2,3;

需求:查询 Blog 表中 id 分别为 1,2,3 的博客信息

BlogMapper.java

List<Blog> queryBlogForeach(Map map);

*Mapper.xml

<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <!--
       collection:指定输入对象中的集合属性
       item:每次遍历生成的对象
       open:开始遍历时的拼接字符串
       close:结束时拼接的字符串
       separator:遍历对象之间需要拼接的字符串
       select * from blog where 1=1 and (id=1 or id=2 or id=3)
     -->
        <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
            id = #{id}
        </foreach>
    </where>
</select>

BlogMapperTest.java

@Test
public void testQueryBlogForeach() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

    HashMap map = new HashMap();
    List<String> ids = new ArrayList<>();
    ids.add("1");
    ids.add("2");
    ids.add("3");
    map.put("ids", ids);

    List<Blog> blogs = mapper.queryBlogForeach(map);

    System.out.println(blogs);

    sqlSession.commit();
    sqlSession.close();
}

缓存

Mybatis 缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
  • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存
    • 默认情况下,只有一级缓存开启(SqlSession 级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于 namespace 级别的缓存
    • 为了提高扩展性,MyBatis 定义了缓存接口 Cache。我们可以通过实现 Cache 接口来自定义二级缓存

一级缓存

一级缓存也叫本地缓存:

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
测试

1、在 Mybatis 中加入日志,方便测试结果

2、编写接口方法

//根据id查询用户
User queryUserById(@Param("id") int id);

3、接口对应的Mapper文件

<select id="queryUserById" resultType="user">
    select * from user where id = #{id}
</select>

4、测试

@Test
public void testQueryUserById() {
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);
    User user2 = mapper.queryUserById(1);
    System.out.println(user2);
    System.out.println(user == user2);

    session.close();
}

5、结果分析

img


一级缓存失效的四种情况

一级缓存是 SqlSession 级别的缓存,是一直开启的,我们关闭不了它;

一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

1、sqlSession不同

@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    SqlSession session2 = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    UserMapper mapper2 = session2.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);
    User user2 = mapper2.queryUserById(1);
    System.out.println(user2);
    System.out.println(user==user2);

    session.close();
    session2.close();
}

观察结果:发现发送了两条 SQL 语句!

结论:每个 sqlSession 中的缓存相互独立

2、sqlSession 相同,查询条件不同

@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    UserMapper mapper2 = session.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);
    User user2 = mapper2.queryUserById(2);
    System.out.println(user2);
    System.out.println(user==user2);

    session.close();
}

观察结果:发现发送了两条 SQL 语句!很正常的理解

结论:当前缓存中,不存在这个数据

3、sqlSession 相同,两次查询之间执行了增删改操作!

增加方法

//修改用户
int updateUser(Map map);

编写SQL

<update id="updateUser" parameterType="map">
    update user set name = #{name} where id = #{id}
</update>

测试

@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);

    HashMap map = new HashMap();
    map.put("name","kuangshen");
    map.put("id",4);
    mapper.updateUser(map);

    User user2 = mapper.queryUserById(1);
    System.out.println(user2);

    System.out.println(user==user2);

    session.close();
}

观察结果:查询在中间执行了增删改操作后,重新执行了

结论:因为增删改操作可能会对当前数据产生影响

4、sqlSession 相同,手动清除一级缓存

@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);

    session.clearCache();//手动清除缓存

    User user2 = mapper.queryUserById(1);
    System.out.println(user2);

    System.out.println(user==user2);

    session.close();
}
一级缓存就是一个 map

二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于 namespace 级别的缓存,一个名称空间,对应一个二级缓存;
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中;
使用步骤

1、开启全局缓存 mybatis-config.xml

<setting name="cacheEnabled" value="true"/>

2、去每个mapper.xml中配置使用二级缓存,这个配置非常简单;*Mapper.xml

<cache eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>
<!-- 这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。 -->

3、代码测试

  • 所有的实体类先实现序列化接口
  • 测试代码
@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    SqlSession session2 = MybatisUtils.getSession();

    UserMapper mapper = session.getMapper(UserMapper.class);
    UserMapper mapper2 = session2.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);
    session.close();

    User user2 = mapper2.queryUserById(1);
    System.out.println(user2);
    System.out.println(user==user2);

    session2.close();
}
结论
  • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
  • 查出的数据都会被默认先放在一级缓存中
  • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中
缓存原理图

img

EhCache

第三方缓存实现--EhCache: 查看百度百科

Ehcache是一种广泛使用的java分布式缓存,用于通用缓存;

要在应用程序中使用Ehcache,需要引入依赖的jar包

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>

在mapper.xml中使用对应的缓存即可

<mapper namespace = “org.acme.FooMapper” > 
    <cache type = “org.mybatis.caches.ehcache.EhcacheCache” /> 
</mapper>

编写 ehcache.xml 文件,如果在加载时未找到 /ehcache.xml 资源或出现问题,则将使用默认配置。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
      diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
      user.home – 用户主目录
      user.dir – 用户当前工作目录
      java.io.tmpdir – 默认临时文件路径
    -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>

    <defaultCache
                  eternal="false"
                  maxElementsInMemory="10000"
                  overflowToDisk="false"
                  diskPersistent="false"
                  timeToIdleSeconds="1800"
                  timeToLiveSeconds="259200"
                  memoryStoreEvictionPolicy="LRU"/>

    <cache
           name="cloud_user"
           eternal="false"
           maxElementsInMemory="5000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           memoryStoreEvictionPolicy="LRU"/>
    <!--
      defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
    -->
    <!--
     name:缓存名称。
     maxElementsInMemory:缓存最大数目
     maxElementsOnDisk:硬盘最大缓存个数。
     eternal:对象是否永久有效,一但设置了,timeout将不起作用。
     overflowToDisk:是否保存到磁盘,当系统当机时
     timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
     timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
     diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
     diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
     diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
     memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
     clearOnFlush:内存数量最大时是否清除。
     memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
     FIFO,first in first out,这个是大家最熟的,先进先出。
     LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
     LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
  -->
</ehcache>
查看原文

赞 0 收藏 0 评论 0

LP学长 关注了用户 · 7月30日

夜尽天明 @biaochenxuying

公众号:全栈修炼

转载文章,请联系笔者。

关注 616

LP学长 收藏了文章 · 7月29日

React入门看这篇就够了

react - JSX

React 背景介绍

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。

什么是React

  • A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES

    • 用来构建UI的 JavaScript库
    • React 不是一个 MVC 框架,仅仅是视图(V)层的库
  • React 官网
  • React 中文文档

特点

  • 1 使用 JSX语法 创建组件,实现组件化开发,为函数式的 UI 编程方式打开了大门
  • 2 性能高的让人称赞:通过 diff算法虚拟DOM 实现视图的高效更新
  • 3 HTML仅仅是个开始
> JSX --TO--> EveryThing

- JSX --> HTML
- JSX --> native ios或android中的组件(XML)
- JSX --> VR
- JSX --> 物联网

为什么要用React

  • 1 使用组件化开发方式,符合现代Web开发的趋势
  • 2 技术成熟,社区完善,配件齐全,适用于大型Web项目(生态系统健全)
  • 3 由Facebook专门的团队维护,技术支持可靠
  • 4 ReactNative - Learn once, write anywhere: Build mobile apps with React
  • 5 使用方式简单,性能非常高,支持服务端渲染
  • 6 React非常火,从技术角度,可以满足好奇心,提高技术水平;从职业角度,有利于求职和晋升,有利于参与潜力大的项目

React中的核心概念

  • 1 虚拟DOM(Virtual DOM)
  • 2 Diff算法(虚拟DOM的加速器,提升React性能的法宝)

虚拟DOM(Vitural DOM)

React将DOM抽象为虚拟DOM,虚拟DOM其实就是用一个对象来描述DOM,通过对比前后两个对象的差异,最终只把变化的部分重新渲染,提高渲染的效率

为什么用虚拟dom,当dom反生更改时需要遍历 而原生dom可遍历属性多大231个 且大部分与渲染无关 更新页面代价太大

VituralDOM的处理方式

  • 1 用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树,插到文档当中
  • 2 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
  • 3 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了

Diff算法

当你使用React的时候,在某个时间点 render() 函数创建了一棵React元素树,
在下一个state或者props更新的时候,render() 函数将创建一棵新的React元素树,
React将对比这两棵树的不同之处,计算出如何高效的更新UI(只更新变化的地方)
<!-- 了解:

有一些解决将一棵树转换为另一棵树的最小操作数算法问题的通用方案。然而,树中元素个数为n,最先进的算法 的时间复杂度为O(n3) 。
如果直接使用这个算法,在React中展示1000个元素则需要进行10亿次的比较。这操作太过昂贵,相反,React基于两点假设,实现了一个O(n)算法,提升性能: -->
  • React中有两种假定:

    • 1 两个不同类型的元素会产生不同的树(根元素不同结构树一定不同)
    • 2 开发者可以通过key属性指定不同树中没有发生改变的子元素

Diff算法的说明 - 1

  • 如果两棵树的根元素类型不同,React会销毁旧树,创建新树
// 旧树
<div>
  <Counter />
</div>

// 新树
<span>
  <Counter />
</span>

执行过程:destory Counter -> insert Counter

Diff算法的说明 - 2

  • 对于类型相同的React DOM 元素,React会对比两者的属性是否相同,只更新不同的属性
  • 当处理完这个DOM节点,React就会递归处理子节点。
// 旧
<div className="before" title="stuff" />
// 新
<div className="after" title="stuff" />
只更新:className 属性

// 旧
<div style={{color: 'red', fontWeight: 'bold'}} />
// 新
<div style={{color: 'green', fontWeight: 'bold'}} />
只更新:color属性

Diff算法的说明 - 3

  • 1 当在子节点的后面添加一个节点,这时候两棵树的转化工作执行的很好
// 旧
<ul>
  <li>first</li>
  <li>second</li>
</ul>

// 新
<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

执行过程:
React会匹配新旧两个<li>first</li>,匹配两个<li>second</li>,然后添加 <li>third</li> tree
  • 2 但是如果你在开始位置插入一个元素,那么问题就来了:
// 旧
<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

// 新
<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

在没有key属性时执行过程:
React将改变每一个子删除重新创建,而非保持 <li>Duke</li> 和 <li>Villanova</li> 不变

key 属性

为了解决以上问题,React提供了一个 key 属性。当子节点带有key属性,React会通过key来匹配原始树和后来的树。
// 旧
<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

// 新
<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>
执行过程:
现在 React 知道带有key '2014' 的元素是新的,对于 '2015' 和 '2016' 仅仅移动位置即可 
  • 说明:key属性在React内部使用,但不会传递给你的组件
  • 推荐:在遍历数据时,推荐在组件中使用 key 属性:<li key={item.id}>{item.name}</li>
  • 注意:key只需要保持与他的兄弟节点唯一即可,不需要全局唯一
  • 注意:尽可能的减少数组index作为key,数组中插入元素的等操作时,会使得效率底下

React的基本使用

  • 安装:npm i -S react react-dom
  • react:react 是React库的入口点
  • react-dom:提供了针对DOM的方法,比如:把创建的虚拟DOM,渲染到页面上
// 1. 导入 react
import React from 'react'
import ReactDOM from 'react-dom'

// 2. 创建 虚拟DOM
// 参数1:元素名称  参数2:元素属性对象(null表示无)  参数3:当前元素的子元素string||createElement() 的返回值
const divVD = React.createElement('div', {
  title: 'hello react'
}, 'Hello React!!!')

// 3. 渲染
// 参数1:虚拟dom对象  参数2:dom对象表示渲染到哪个元素内 参数3:回调函数
ReactDOM.render(divVD, document.getElementById('app'))

createElement()的问题

  • 说明:createElement()方式,代码编写不友好,太复杂
var dv = React.createElement(
  "div",
  { className: "shopping-list" },
  React.createElement(
    "h1",
    null,
    "Shopping List for "
  ),
  React.createElement(
    "ul",
    null,
    React.createElement(
      "li",
      null,
      "Instagram"
    ),
    React.createElement(
      "li",
      null,
      "WhatsApp"
    )
  )
)
// 渲染
ReactDOM.render(dv, document.getElementById('app'))

JSX 的基本使用

  • 注意:JSX语法,最终会被编译为 createElement() 方法
  • 推荐:使用 JSX 的方式创建组件
  • JSX - JavaScript XML
  • 安装:npm i -D babel-preset-react (依赖与:babel-core/babel-loader)
注意:JSX的语法需要通过 babel-preset-react 编译后,才能被解析执行
/* 1 在 .babelrc 开启babel对 JSX 的转换 */
{
  "presets": [
    "env", "react"
  ]
}

/* 2 webpack.config.js */
module: [
  rules: [
    { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ },
  ]
]

/* 3 在 js 文件中 使用 JSX */
const dv = (
  <div title="标题" className="cls container">Hello JSX!</div>
)

/* 4 渲染 JSX 到页面中 */
ReactDOM.render(dv, document.getElementById('app'))

JSX的注意点

  • 注意 1: 如果在 JSX 中给元素添加类, 需要使用 className 代替 class

    • 类似:label 的 for属性,使用htmlFor代替
  • 注意 2:在 JSX 中可以直接使用 JS代码,直接在 JSX 中通过 {} 中间写 JS代码即可
  • 注意 3:在 JSX 中只能使用表达式,但是不能出现 语句!!!
  • 注意 4:在 JSX 中注释语法:{/* 中间是注释的内容 */}

React组件

React 组件可以让你把UI分割为独立、可复用的片段,并将每一片段视为相互独立的部分。
  • 组件是由一个个的HTML元素组成的
  • 概念上来讲, 组件就像JS中的函数。它们接受用户输入(props),并且返回一个React对象,用来描述展示在页面中的内容

React创建组件的两种方式

  • 1 通过 JS函数 创建(无状态组件)
  • 2 通过 class 创建(有状态组件)
函数式组件 和 class 组件的使用场景说明:
1 如果一个组件仅仅是为了展示数据,那么此时就可以使用 函数组件
2 如果一个组件中有一定业务逻辑,需要操作数据,那么就需要使用 class 创建组件,因为,此时需要使用 state

JavaScript函数创建

  • 注意:1 函数名称必须为大写字母开头,React通过这个特点来判断是不是一个组件
  • 注意:2 函数必须有返回值,返回值可以是:JSX对象或null
  • 注意:3 返回的JSX,必须有一个根元素
  • 注意:4 组件的返回值使用()包裹,避免换行问题
function Welcome(props) {
  return (
    // 此处注释的写法 
    <div className="shopping-list">
      {/* 此处 注释的写法 必须要{}包裹 */}
      <h1>Shopping List for {props.name}</h1>
      <ul>
        <li>Instagram</li>
        <li>WhatsApp</li>
      </ul>
    </div>
  )
}

ReactDOM.render(
  <Welcome name="jack" />,
  document.getElementById('app')
)

class创建

在es6中class仅仅是一个语法糖,不是真正的类,本质上还是构造函数+原型 实现继承
// ES6中class关键字的简单使用

// - **ES6中的所有的代码都是运行在严格模式中的**
// - 1 它是用来定义类的,是ES6中实现面向对象编程的新方式
// - 2 使用`static`关键字定义静态属性
// - 3 使用`constructor`构造函数,创建实例属性
// - [参考](http://es6.ruanyifeng.com/#docs/class)

// 语法:
class Person {
  // 实例的构造函数 constructor
  constructor(age){
    // 实例属性
    this.age = age
  }
  // 在class中定义方法 此处为实例方法 通过实例打点调用
  sayHello () {
    console.log('大家好,我今年' + this.age + '了');
  }

  // 静态方法 通过构造函数打点调用 Person.doudou()
  static doudou () {
    console.log('我是小明,我新get了一个技能,会暖床');
  }
}
// 添加静态属性
Person.staticName = '静态属性'
// 实例化对象
const p = new Person(19)
 
 
// 实现继承的方式
 
class American extends Person {
  constructor() {
    // 必须调用super(), super表示父类的构造函数
    super()
    this.skin = 'white'
    this.eyeColor = 'white'
  }
}

// 创建react对象
// 注意:基于 `ES6` 中的class,需要配合 `babel` 将代码转化为浏览器识别的ES5语法
// 安装:`npm i -D babel-preset-env`
 
//  react对象继承字React.Component
class ShoppingList extends React.Component {
  constructor(props) { 
    super(props)
  }
  // class创建的组件中 必须有rander方法 且显示return一个react对象或者null
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
        </ul>
      </div>
    )
  }
}

给组件传递数据 - 父子组件传递数据

  • 组件中有一个 只读的对象 叫做 props,无法给props添加属性
  • 获取方式:函数参数 props
  • 作用:将传递给组件的属性转化为 props 对象中的属性
function Welcome(props){
  // props ---> { username: 'zs', age: 20 }
  return (
    <div>
      <div>Welcome React</div>
      <h3>姓名:{props.username}----年龄是:{props.age}</h3>
    </div>
  )
}

// 给 Hello组件 传递 props:username 和 age(如果你想要传递numb类型是数据 就需要向下面这样)
ReactDOM.reander(<Hello username="zs" age={20}></Hello>, ......)

封装组件到独立的文件中

// 创建Hello2.js组件文件
// 1. 引入React模块
// 由于 JSX 编译后会调用 React.createElement 方法,所以在你的 JSX 代码中必须首先拿到React。
import React from 'react'

// 2. 使用function构造函数创建组件
function Hello2(props){
  return (
    <div>
      <div>这是Hello2组件</div>
      <h1>这是大大的H1标签,我大,我骄傲!!!</h1>
      <h6>这是小小的h6标签,我小,我傲娇!!!</h6>
    </div>
  )
}
// 3. 导出组件
export default Hello2

// app.js中   使用组件:
import Hello2 from './components/Hello2'

props和state

props

  • 作用:给组件传递数据,一般用在父子组件之间
  • 说明:React把传递给组件的属性转化为一个对象并交给 props
  • 特点:props是只读的,无法给props添加或修改属性
  • props.children:获取组件的内容,比如:

    • <Hello>组件内容</Hello> 中的 组件内容
// props 是一个包含数据的对象参数,不要试图修改 props 参数
// 返回值:react元素
function Welcome(props) {
  // 返回的 react元素中必须只有一个根元素
  return <div>hello, {props.name}</div>
}

class Welcome extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

state

状态即数据
  • 作用:用来给组件提供组件内部使用的数据
  • 注意:只有通过class创建的组件才具有状态
  • 注意:状态是私有的,完全由组件来控制
  • 注意:不要在 state 中添加 render() 方法中不需要的数据,会影响渲染性能!

    • 可以将组件内部使用但是不渲染在视图中的内容,直接添加给 this
  • 注意:不要在 render() 方法中调用 setState() 方法来修改state的值

    • 但是可以通过 this.state.name = 'rose' 方式设置state(不推荐!!!!)
// 例:
class Hello extends React.Component {
  constructor() {
    // es6继承必须用super调用父类的constructor
    super()

    this.state = {
      gender: 'male'
    }
  }

  render() {
    return (
      <div>性别:{ this.state.gender }</div>
    )
  }
}

JSX语法转化过程

// 1、JSX
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
)

// 2、JSX -> createElement
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
)

// React elements: 使用对象的形式描述页面结构
// Note: 这是简化后的对象结构
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
  },
  children: ['Hello, world']
}

评论列表案例

  • 巩固有状态组件和无状态组件的使用
  • 两个组件:<CommentList></CommentList><Comment></Comment>
[
  { user: '张三', content: '哈哈,沙发' },
  { user: '张三2', content: '哈哈,板凳' },
  { user: '张三3', content: '哈哈,凉席' },
  { user: '张三4', content: '哈哈,砖头' },
  { user: '张三5', content: '哈哈,楼下山炮' }
]

// 属性扩展
<Comment {...item} key={i}></Comment>

style样式

// 1. 直接写行内样式:
<li style={{border:'1px solid red', fontSize:'12px'}}></li>

// 2. 抽离为对象形式
var styleH3 = {color:'blue'}
var styleObj = {
  liStyle:{border:'1px solid red', fontSize:'12px'},
  h3Style:{color:'green'}
}

<li style={styleObj.liStyle}>
  <h3 style={styleObj.h3Style}>评论内容:{props.content}</h3>
</li>

// 3. 使用样式表定义样式:
import '../css/comment.css'
<p className="pUser">评论人:{props.user}</p>

相关文章

组件的生命周期

  • 简单说:一个组件从开始到最后消亡所经历的各种状态,就是一个组件的生命周期

组件生命周期函数的定义:从组件被创建,到组件挂载到页面上运行,再到页面关闭组件被卸载,这三个阶段总是伴随着组件各种各样的事件,那么这些事件,统称为组件的生命周期函数!

组件生命周期函数总览

  • 组件的生命周期包含三个阶段:创建阶段(Mounting)、运行和交互阶段(Updating)、卸载阶段(Unmounting)
  • Mounting:
constructor()
componentWillMount()
render()
componentDidMount()
  • Updating
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate()
  • Unmounting
componentWillUnmount()

组件生命周期 - 创建阶段(Mounting)

  • 特点:该阶段的函数只执行一次

constructor()

  • 作用:1 获取props 2 初始化state
  • 说明:通过 constructor() 的参数props获取
  • 设置state和props
class Greeting extends React.Component {
  constructor(props) {
    // 获取 props
    super(props)
    // 初始化 state
    this.state = {
      count: props.initCount
    }
  }
}

// 初始化 props
// 语法:通过静态属性 defaultProps 来初始化props
Greeting.defaultProps = {
  initCount: 0
};

componentWillMount()

  • 说明:组件被挂载到页面之前调用,其在render()之前被调用,因此在这方法里同步地设置状态将不会触发重渲染
  • 注意:无法获取页面中的DOM对象
  • 注意:可以调用 setState() 方法来改变状态值
  • 用途:发送ajax请求获取数据
componentWillMount() {
  console.warn(document.getElementById('btn')) // null
  this.setState({
    count: this.state.count + 1
  })
}

render()

  • 作用:渲染组件到页面中,无法获取页面中的DOM对象
  • 注意:不要在render方法中调用 setState() 方法,否则会递归渲染

    • 原因说明:状态改变会重新调用render()render()又重新改变状态
render() {
  console.warn(document.getElementById('btn')) // null

  return (
    <div>
      <button id="btn" onClick={this.handleAdd}>打豆豆一次</button>
      {
        this.state.count === 4
        ? null
        : <CounterChild initCount={this.state.count}></CounterChild>
      }
    </div>
  )
}

componentDidMount()

  • 1 组件已经挂载到页面中
  • 2 可以进行DOM操作,比如:获取到组件内部的DOM对象
  • 3 可以发送请求获取数据
  • 4 可以通过 setState() 修改状态的值
  • 注意:在这里修改状态会重新渲染
componentDidMount() {
  // 此时,就可以获取到组件内部的DOM对象
  console.warn('componentDidMount', document.getElementById('btn'))
}

组件生命周期 - 运行阶段(Updating)

  • 特点:该阶段的函数执行多次
  • 说明:每当组件的props或者state改变的时候,都会触发运行阶段的函数

componentWillReceiveProps()

  • 说明:组件接受到新的props前触发这个方法
  • 参数:当前组件props
  • 可以通过 this.props 获取到上一次的值
  • 使用:若你需要响应属性的改变,可以通过对比this.propsnextProps并在该方法中使用this.setState()处理状态改变
  • 注意:修改state不会触发该方法
componentWillReceiveProps(nextProps) {
  console.warn('componentWillReceiveProps', nextProps)
}

shouldComponentUpdate()

  • 作用:根据这个方法的返回值决定是否重新渲染组件,返回true重新渲染,否则不渲染
  • 优势:通过某个条件渲染组件,降低组件渲染频率,提升组件性能
  • 说明:如果返回值为false,那么,后续render()方法不会被调用
  • 注意:这个方法必须返回布尔值!!!
  • 场景:根据随机数决定是否渲染组件
// - 参数:
//   - 第一个参数:最新属性对象
//   - 第二个参数:最新状态对象
shouldComponentUpdate(nextProps, nextState) {
  console.warn('shouldComponentUpdate', nextProps, nextState)

  return nextState.count % 2 === 0
}

componentWillUpdate()

  • 作用:组件将要更新
  • 参数:最新的属性和状态对象
  • 注意:不能修改状态 否则会循环渲染
componentWillUpdate(nextProps, nextState) {
  console.warn('componentWillUpdate', nextProps, nextState)
}

render() 渲染

  • 作用:重新渲染组件,与Mounting阶段的render是同一个函数
  • 注意:这个函数能够执行多次,只要组件的属性或状态改变了,这个方法就会重新执行

componentDidUpdate()

  • 作用:组件已经被更新
  • 参数:旧的属性和状态对象
componentDidUpdate(prevProps, prevState) {
  console.warn('componentDidUpdate', prevProps, prevState)
}

组件生命周期 - 卸载阶段(Unmounting)

  • 组件销毁阶段:组件卸载期间,函数比较单一,只有一个函数,这个函数也有一个显著的特点:组件一辈子只能执行依次!
  • 使用说明:只要组件不再被渲染到页面中,那么这个方法就会被调用( 渲染到页面中 -> 不再渲染到页面中 )

componentWillUnmount()

  • 作用:在卸载组件的时候,执行清理工作,比如

    • 1 清除定时器
    • 2 清除componentDidMount创建的DOM对象

React - createClass(不推荐)

  • React.createClass({}) 方式,创建有状态组件,该方式已经被废弃!!!
  • 通过导入 require('create-react-class'),可以在不适用ES6的情况下,创建有状态组件
  • getDefaultProps() 和 getInitialState() 方法:是 createReactClass() 方式创建组件中的两个函数
  • React without ES6
  • React 不适用ES6
var createReactClass = require('create-react-class');
var Greeting = createReactClass({
  // 初始化 props
  getDefaultProps: function() {
    console.log('getDefaultProps');
    return {
      title: 'Basic counter!!!'
    }
  },

  // 初始化 state
  getInitialState: function() {
    console.log('getInitialState');
    return {
      count: 0
    }
  },

  render: function() {
    console.log('render');
    return (
      <div>
        <h1>{this.props.title}</h1>
        <div>{this.state.count}</div>
        <input type='button' value='+' onClick={this.handleIncrement} />
      </div>
    );
  },

  handleIncrement: function() {
    var newCount = this.state.count + 1;
    this.setState({count: newCount});
  },

  propTypes: {
    title: React.PropTypes.string
  }
});

ReactDOM.render(
  React.createElement(Greeting),
  document.getElementById('app')
);

state和setState

  • 注意:使用 setState() 方法修改状态,状态改变后,React会重新渲染组件
  • 注意:不要直接修改state属性的值,这样不会重新渲染组件!!!
  • 使用:1 初始化state 2 setState修改state
// 修改state(不推荐使用)
// https://facebook.github.io/react/docs/state-and-lifecycle.html#do-not-modify-state-directly
this.state.test = '这样方式,不会重新渲染组件';
constructor(props) {
  super(props)

  // 正确姿势!!!
  // -------------- 初始化 state --------------
  this.state = {
    count: props.initCount
  }
}

componentWillMount() {
  // -------------- 修改 state 的值 --------------
  // 方式一:
  this.setState({
    count: this.state.count + 1
  })

  this.setState({
    count: this.state.count + 1
  }, function(){
    // 由于 setState() 是异步操作,所以,如果想立即获取修改后的state
    // 需要在回调函数中获取
    // https://doc.react-china.org/docs/react-component.html#setstate
  });

  // 方式二:
  this.setState(function(prevState, props) {
    return {
      counter: prevState.counter + props.increment
    }
  })

  // 或者 - 注意: => 后面需要带有小括号,因为返回的是一个对象
  this.setState((prevState, props) => ({
    counter: prevState.counter + props.increment
  }))
}

组件绑定事件

  • 1 通过React事件机制 onClick 绑定
  • 2 JS原生方式绑定(通过 ref 获取元素)

    • 注意:ref 是React提供的一个特殊属性
    • ref的使用说明:react ref

React中的事件机制 - 推荐

  • 注意:事件名称采用驼峰命名法
  • 例如:onClick 用来绑定单击事件
<input type="button" value="触发单击事件"
  onClick={this.handleCountAdd}
  onMouseEnter={this.handleMouseEnter}
/>

JS原生方式 - 知道即可

  • 说明:给元素添加 ref 属性,然后,获取元素绑定事件
// JSX
// 将当前DOM的引用赋值给 this.txtInput 属性
<input ref={ input => this.txtInput = input } type="button" value="我是豆豆" />

componentDidMount() {
  // 通过 this.txtInput 属性获取元素绑定事件
  this.txtInput.addEventListener(() => {
    this.setState({
      count:this.state.count + 1
    })
  })
}

事件绑定中的this

  • 1 通过 bind 绑定
  • 2 通过 箭头函数 绑定

通过bind绑定

  • 原理:bind能够调用函数,改变函数内部this的指向,并返回一个新函数
  • 说明:bind第一个参数为返回函数中this的指向,后面的参数为传给返回函数的参数
// 自定义方法:
handleBtnClick(arg1, arg2) {
  this.setState({
    msg: '点击事件修改state的值' + arg1 + arg2
  })
}

render() {
  return (
    <div>
      <button onClick={
        // 无参数
        // this.handleBtnClick.bind(this)

        // 有参数
        this.handleBtnClick.bind(this, 'abc', [1, 2])
      }>事件中this的处理</button>
      <h1>{this.state.msg}</h1>
    </div>
  )
}
  • 在构造函数中使用bind
constructor() {
  super()

  this.handleBtnClick = this.handleBtnClick.bind(this)
}

// render() 方法中:
<button onClick={ this.handleBtnClick }>事件中this的处理</button>

通过箭头函数绑定

  • 原理:箭头函数中的this由所处的环境决定,自身不绑定this
<input type="button" value="在构造函数中绑定this并传参" onClick={
  () => { this.handleBtnClick('参数1', '参数2') }
} />

handleBtnClick(arg1, arg2) {
  this.setState({
    msg: '在构造函数中绑定this并传参' + arg1 + arg2
  });
}

受控组件

在HTML当中,像input,textareaselect这类表单元素会维持自身状态,并根据用户输入进行更新。
在React中,可变的状态通常保存在组件的state中,并且只能用 setState() 方法进行更新.
React根据初始状态渲染表单组件,接受用户后续输入,改变表单组件内部的状态。
因此,将那些值由React控制的表单元素称为:受控组件。
  • 受控组件的特点:

    • 1 表单元素
    • 2 由React通过JSX渲染出来
    • 3 由React控制值的改变,也就是说想要改变元素的值,只能通过React提供的方法来修改
  • 注意:只能通过setState来设置受控组件的值
// 模拟实现文本框数据的双向绑定
<input type="text" value={this.state.msg} onChange={this.handleTextChange}/>

// 当文本框内容改变的时候,触发这个事件,重新给state赋值
handleTextChange = event => {
  console.log(event.target.value)

  this.setState({
    msg: event.target.value
  })
}

评论列表案例

[
  {name: '小明', content: '沙发!!!'},
  {name: '小红', content: '小明,居然是你'},
  {name: '小刚', content: '小明,放学你别走!!!'},
]

props校验

  • 作用:通过类型检查,提高程序的稳定性
  • 命令:npm i -S prop-types
  • 类型校验文档
  • 使用:给类提供一个静态属性 propTypes(对象),来约束props
// 引入模块
import PropTypes from 'prop-types'

// ...以下代码是类的静态属性:
// propTypes 静态属性的名称是固定的!!!
static propTypes = {
  initCount: PropTypes.number, // 规定属性的类型
  initAge: PropTypes.number.isRequired // 规定属性的类型,且规定为必传字段
}

React 单向数据流

  • React 中采用单项数据流
  • 数据流动方向:自上而下,也就是只能由父组件传递到子组件
  • 数据都是由父组件提供的,子组件想要使用数据,都是从父组件中获取的
  • 如果多个组件都要使用某个数据,最好将这部分共享的状态提升至他们最近的父组件当中进行管理
  • 单向数据流
  • 状态提升
react中的单向数据流动:
1 数据应该是从上往下流动的,也就是由父组件将数据传递给子组件
2 数据应该是由父组件提供,子组件要使用数据的时候,直接从子组件中获取

在我们的评论列表案例中:数据是由CommentList组件(父组件)提供的
子组件 CommentItem 负责渲染评论列表,数据是由 父组件提供的
子组件 CommentForm 负责获取用户输入的评论内容,最终也是把用户名和评论内容传递给了父组件,由父组件负责处理这些数据( 把数据交给 CommentItem 由这个组件负责渲染 )

组件通讯

  • 父 -> 子:props
  • 子 -> 父:父组件通过props传递回调函数给子组件,子组件调用函数将数据作为参数传递给父组件
  • 兄弟组件:因为React是单向数据流,因此需要借助父组件进行传递,通过父组件回调函数改变兄弟组件的props
  • React中的状态管理: flux(提出状态管理的思想) -> Redux -> mobx
  • Vue中的状态管理: Vuex
  • 简单来说,就是统一管理了项目中所有的数据,让数据变的可控
  • 组件通讯

Context特性

  • 注意:如果不熟悉React中的数据流,不推荐使用这个属性

    • 这是一个实验性的API,在未来的React版本中可能会被更改
  • 作用:跨级传递数据(爷爷给孙子传递数据),避免向下每层手动地传递props
  • 说明:需要配合PropTypes类型限制来使用
class Grandfather extends React.Component {
  // 类型限制(必须),静态属性名称固定
  static childContextTypes = {
    color: PropTypes.string.isRequired
  }

  // 传递给孙子组件的数据
  getChildContext() {
    return {
      color: 'red'
    }
  }

  render() {
    return (
      <Father></Father>
    )
  }
}

class Child extends React.Component {
  // 类型限制,静态属性名字固定
  static contextTypes = {
    color: PropTypes.string
  }

  render() {
    return (
      // 从上下文对象中获取爷爷组件传递过来的数据
      <h1 style={{ color: this.context.color }}>爷爷告诉文字是红色的</h1>
    )
  }
}

class Father extends React.Component {
  render() {
    return (
      <Child></Child>
    )
  }
}

react-router

基本概念说明

  • Router组件本身只是一个容器,真正的路由要通过Route组件定义

使用步骤

  • 1 导入路由组件
  • 2 使用 <Router></Router> 作为根容器,包裹整个应用(JSX)

    • 在整个应用程序中,只需要使用一次
  • 3 使用 <Link to="/movie"></Link> 作为链接地址,并指定to属性
  • 4 使用 <Route path="/" compoent={Movie}></Route> 展示路由内容
// 1 导入组件
import {
  HashRouter as Router,
  Link, Route
} from 'react-router-dom'

// 2 使用 <Router>
<Router>

    // 3 设置 Link
    <Menu.Item key="1"><Link to="/">首页</Link></Menu.Item>
    <Menu.Item key="2"><Link to="/movie">电影</Link></Menu.Item>
    <Menu.Item key="3"><Link to="/about">关于</Link></Menu.Item>

    // 4 设置 Route
    // exact 表示:绝对匹配(完全匹配,只匹配:/)
    <Route exact path="/" component={HomeContainer}></Route>
    <Route path="/movie" component={MovieContainer}></Route>
    <Route path="/about" component={AboutContainer}></Route>

</Router>

注意点

  • <Router></Router>:作为整个组件的根元素,是路由容器,只能有一个唯一的子元素
  • <Link></Link>:类似于vue中的<router-link></router-link>标签,to 属性指定路由地址
  • <Route></Route>:类似于vue中的<router-view></router-view>,指定路由内容(组件)展示位置

路由参数

  • 配置:通过Route中的path属性来配置路由参数
  • 获取:this.props.match.params 获取
// 配置路由参数
<Route path="/movie/:movieType"></Route>

// 获取路由参数
const type = this.props.match.params.movieType

路由跳转

  • react router - history
  • history.push() 方法用于在JS中实现页面跳转
  • history.go(-1) 用来实现页面的前进(1)和后退(-1)
this.props.history.push('/movie/movieDetail/' + movieId)

fetch

  • 作用:Fetch 是一个现代的概念, 等同于 XMLHttpRequest。它提供了许多与XMLHttpRequest相同的功能,但被设计成更具可扩展性和高效性。
  • fetch() 方法返回一个Promise对象

fetch 基本使用

/*
  通过fetch请求回来的数据,是一个Promise对象.
  调用then()方法,通过参数response,获取到响应对象
  调用 response.json() 方法,解析服务器响应数据
  再次调用then()方法,通过参数data,就获取到数据了
*/
fetch('/api/movie/' + this.state.movieType)
  // response.json() 读取response对象,并返回一个被解析为JSON格式的promise对象
  .then((response) => response.json())
  // 通过 data 获取到数据
  .then((data) => {
    console.log(data);
    this.setState({
      movieList: data.subjects,
      loaing: false
    })
  })

跨域获取数据的三种常用方式

  • 1 JSONP
  • 2 代理
  • 3 CORS

JSONP

  • 安装:npm i -S fetch-jsonp
  • 利用JSONP实现跨域获取数据,只能获取GET请求
  • fetch-jsonp
  • fetch-jsonp
  • 限制:1 只能发送GET请求 2 需要服务端支持JSONP请求
/* movielist.js */
fetchJsonp('https://api.douban.com/v2/movie/in_theaters')
  .then(rep => rep.json())
  .then(data => { console.log(data) })

代理

  • webpack-dev-server 代理配置如下:
  • 问题:webpack-dev-server 是开发期间使用的工具,项目上线了就不再使用 webpack-dev-server
  • 解决:项目上线后的代码,也是会部署到一个服务器中,这个服务器配置了代理功能即可(要求两个服务器中配置的代理规则相同)
// webpack-dev-server的配置
devServer: {
  // https://webpack.js.org/configuration/dev-server/#devserver-proxy
  // https://github.com/chimurai/http-proxy-middleware#http-proxy-options
  // http://www.jianshu.com/p/3bdff821f859
  proxy: {
    // 使用:/api/movie/in_theaters
    // 访问 ‘/api/movie/in_theaters’ ==> 'https://api.douban.com/v2/movie/in_theaters'
    '/api': {
      // 代理的目标服务器地址
      target: 'https://api.douban.com/v2',
      // https请求需要该设置
      secure: false,
      // 必须设置该项
      changeOrigin: true,
      // '/api/movie/in_theaters' 路径重写为:'/movie/in_theaters'
      pathRewrite: {"^/api" : ""}
    }
  }
}

/* movielist.js */
fetch('/api/movie/in_theaters')
  .then(function(data) {
    // 将服务器返回的数据转化为 json 格式
    return data.json()
  })
  .then(function(rep) {
    // 获取上面格式化后的数据
    console.log(rep);
  })

CORS - 服务器端配合

// 通过Express的中间件来处理所有请求
app.use('*', function (req, res, next) {
  // 设置请求头为允许跨域
  res.header('Access-Control-Allow-Origin', '*');

  // 设置服务器支持的所有头信息字段
  res.header('Access-Control-Allow-Headers', 'Content-Type,Content-Length, Authorization,Accept,X-Requested-With');
  // 设置服务器支持的所有跨域请求的方法
  res.header('Access-Control-Allow-Methods', 'POST,GET');
  // next()方法表示进入下一个路由
  next();
});

redux

  • 状态管理工具,用来管理应用中的数据

核心

  • Action:行为的抽象,视图中的每个用户交互都是一个action

    • 比如:点击按钮
  • Reducer:行为响应的抽象,也就是:根据action行为,执行相应的逻辑操作,更新state

    • 比如:点击按钮后,添加任务,那么,添加任务这个逻辑放到 Reducer 中
    • 1 创建State
  • Store:

    • 1 Redux应用只能有一个store
    • 2 getState():获取state
    • 3 dispatch(action):更新state
/* action */

// 在 redux 中,action 就是一个对象
// action 必须提供一个:type属性,表示当前动作的标识
// 其他的参数:表示这个动作需要用到的一些数据
{ type: 'ADD_TODO', name: '要添加的任务名称' }

// 这个动作表示要切换任务状态
{ type: 'TOGGLE_TODO', id: 1 }
/* reducer */

// 第一个参数:表示状态(数据),我们需要给初始状态设置默认值
// 第二个参数:表示 action 行为
function todo(state = [], action) {
  switch(action.type) {
    case 'ADD_TODO':
      state.push({ id: Math.random(), name: action.name, completed: false })
      return state
    case 'TOGGLE_TODO':
      for(var i = 0; i < state.length; i++) {
        if (state[i].id === action.id) {
          state[i].completed = !state[i].completed
          break
        }
      }
      return state
    default:
      return state
  }
}

// 要执行 ADD_TODO 这个动作:
dispatch( { type: 'ADD_TODO', name: '要添加的任务名称' } )

// 内部会调用 reducer
todo(undefined, { type: 'ADD_TODO', name: '要添加的任务名称' })

同事的博客各种干货值得收藏

查看原文

认证与成就

  • 获得 0 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2月25日
个人主页被 97 人浏览