搞懂javascript面向对象之完结篇

136 阅读3分钟

函数柯里化

接受多个参数的函数转化为接受一部分参数的新函数,余下的参数保存起来,当函数调用时,返回传入的参数与保存的参数共同执行的结果

  • 案例一:
//参数有限
function add(num1,num2){
    return num1+num2;
}


function add5(num){
    return add(5,num)
}
  • 案例二:
//函数柯里化
function curry(fn){
    //缓存数组slice方法Array.prototype.slice
    var Slice=[].slice

    //从第二个参数开始截取参数
    //var a=[1,2,3].slice(1);console.log(a)->[2,3]
    var args=Slice.call(arguments,1);
    console.log(args)

    /**var add5=curry(add,5)
     * 这里的args=[5]
     * 
     * 这里的传进这个函数的参数是7
     * console.log(add5(7))
     */
    return function(){
        //将传进这个函数的参数转换为数组
        //这里是[7]
        var addArgs=Slice.call(arguments);

        //拼接参数--这里是[5,7]
        var allArgs=args.concat(addArgs);

        console.log(allArgs)
        console.log(fn)
        return fn.apply(null,allArgs)
    }
}

//柯里化
// var add5=curry(add,5)
// console.log(add5(7))  //12

var add6=curry(add,7,8)
console.log(add6(8))

巧妙利用return来退出循环

var print=function(i,j){console.log(i,j)};
var func=function(){
    for(var i=0;i<10;i++){
        for(var j=0;j<10;j++){
            if(i * j >30){
                return print(i,j)
            }
        }
    }

};
func()
// return 跳出整个for循环(最外层)

最少知识原则

  • 减少对象之间的之间的联系,封装在很大的成都上表达的是数据的隐藏
  • 一个模块或者对象可以将内部的数据或者实现, 细节隐藏起来,只暴露必要的接口API供外界访问, 对象之间难免产生联系,当一个对象必须引用另外一个对象的时候,可以让对象只暴露必要的接口,让对象的联系控制在最小的范围之内(比如闭包,作用域)
        var A=function(){
            a1();
            a2();
        }

        var B=function(){
            b1();
            b2();
        }

        var total=function(){
            A();
            B();
        }

        total();

var mult=(
    function(){
        var cache={};
        return function(){
            var args=Array.prototype.join.call(arguments,',')
            // console.log('args',args) //'1,2,3'
            //缓存结果
            if(cache[args]){
                return cache[args]
            }
            var a=1;
            for(var i=0,l=arguments.length;i<l;i++){
                a=a*arguments[i]
            }
            return cache[args]=a;
        }
    }
)()
mult(1,2,3)

利用对象的多态消除分支语句

  • 找出不变的地方
//是动物就会叫
var MakeSound=function(animal){
    animal.sound()
}

var Duck=function(){}

Duck.prototype.sound=function(){
    console.log('鸭子叫')
}

var Chicken=function(){}

Chicken.prototype.sound=function(){
    console.log('鸡叫')
}

MakeSound(new Duck())
MakeSound(new Chicken)
  • 找出变化的地方
var arrMap=function(arr,cb){
    var i=0,len=arr.length,value,res=[];
    for(;i<len;i++){
        //回调函数和数组是变化的地方
        value=cb(i,arr[i]);
        res.push(value)
    }

    return res;
}

var a1=arrMap([1,2,3],function(i,v){
    return v*5
})

console.log(a1) //[5,10,15]

传递对象参数代替过长的参数列表

var userInfo=function(id,name,age,sex){
    console.log('id=' +id)
    console.log('name=' +name)
    console.log('age=' +age)
    console.log('sex=' +sex)
}

// userInfo(1,2,3,4)

//改造后:
var userInfo1=function(obj){
    console.log('id=' +obj.id)
    console.log('name=' +obj.name)
    console.log('age=' +obj.age)
    console.log('sex=' +obj.sex)
}
userInfo1({
    id:1,
    name:1,
    age:1,
    sex:1,
})

避免(减少)传入不必要传入的参数的数量

function add(num1,num2,total){
    return num1+num2
}

function add(num1,num2){
    return total=num1+num2
}

合理使用链式调用

  • 即让方法调用结束后返回对象自身,如果链条的结构相对稳定后期不易发生改变使用链条调用是可以的,但是如果链条的结构在后期容易发生改变那么使用链条会使调式代码变得困难
var User={
    id:null,
    name:null,
    setId:function(id){
        this.id=id;
        console.log(this.id)
        return this;
    },
    setName:function(name){
        this.name=name;
        console.log(this.name)
        return this;
    }
}

User.setId(1111).setName('lth')

认知异步方法

  • 案例一:以setTimeout来认知
setTimeout(function(){
    console.log('first')
},0)

console.log('second')

//执行顺序为 second first

分解大型类

  • 分解前
var Spirit=function(name){
    this.name=name
}

Spirit.prototype.attack=function(type){ //根据执行不同的攻击类型执行不同的动作
    if(type==='1'){
        console.log(this.name + ' 执行攻击一')
    }else if(type==='2'){
        console.log(this.name + ' 执行攻击二')
    }
}

var spirit=new Spirit('lth');
spirit.attack('1')
spirit.attack('2')
  • 我们会发现后期attack这个类是不断拓展的会十分庞大,可以单独把attack抽离为一个类,分解后:
var Attack=function(spirit){
    //传进来的是一个实例化对象
    this.spirit=spirit;
}

Attack.prototype.start=function(type){
    return this.list[type].call(this)
    //这里的this指向Attack
}

Attack.prototype.list={
    //这个实例化对象上面有name属性
    '1':function(){console.log(this.spirit.name + ' 执行攻击一')},
    '2':function(){console.log(this.spirit.name + ' 执行攻击二')}
}


var Spirit=function(name){
    this.name=name;
    //这里的this指的是Spirit对象
    this.attackObj=new Attack(this)
}

Spirit.prototype.attack=function(type){
    this.attackObj.start(type)
}


var spirit=new Spirit('lth');
spirit.attack('1')
spirit.attack('2')

把分支语句提炼成函数

var getPrice=function(price){
    var date=new Date();
    if(date.getMonth() >=6 && date.getMonth()<=9){
        return price * 0.5
    }
    return price;
}


//把分支语句提炼为函数
var isSummer=function(){
    var date=new Date();
    return date.getMonth() >=6 && date.getMonth()<=9;
}

var getPrice=function(price){
    if(isSummer()){
        return price*0.5
    }

    return price;
}

单一职责原则

  • 一个对象(方法)只做一件事
//这个对象只负责创建img标签
var creatImg=(function(){
    var img=document.createElement('img');
    document.body.appendChild(img);
    return {
        setSrc:function(src){
            img.src=ssrc
        }
    }
})()

//这个对象只负责预加载图片
var proxyImg=(function(){
    var img=new Image;
    img.onload=function(){
        creatImg.setSrc(this.src)
    }

    return {
        setSrc:function(src){
            creatImg.setSrc('./xxx.png');
            img.src=src;
        }
    }
})()

proxyImg.setSrc('https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/26476e28d9a94270b80cddb03e993644~tplv-k3u1fbpfcp-watermark.image')
  • 案例二反例:这个函数把渲染数据和遍历对象data的职责混在了一起,如果data数据格式改变如果改变为对象格式则此方法不在适用
//反例:这个函数把渲染数据和遍历对象data的职责混在了一起
//如果data数据格式改变如果改变为对象格式则此方法不在适用
var appendDiv=function(data){
    for(var i=0,l=data.length;i<l;i++){
        var div=document.createElement('div');
        div.innerHTML=data[i];
        document.body.appendChild(div);
    }
}

appendDiv([1,2,3,4,5])
  • 案例二反例改造为:
var each=function(obj,fn){
    var value,length=obj.length,i=0;

    //如果该obj有length或者该obj的实例是Array判定为数组
    if(length || obj instanceof Array){
        for(;i<length;i++){
            fn.call(obj[i],i,obj[i])
        }
    }else{
        //否则判定为对象
        for(i in obj){
            //value可以控制迭代器何时结束
            value=fn.call(obj[i],i,obj[i])
        }
    }
    return obj;
}

var appendDiv=function(data){
    each(data,function(i,n){
        var div=document.createElement('div');
        div.innerHTML=data[i];
        document.body.appendChild(div);
    })
}


// appendDiv([1,2,3])
appendDiv({a:1,b:2,c:3})
  • 根据单一职责的惰性单例的改造, 这个函数把管理单例的职责和创建登录块的职责封装在了一起,我们可以把这两个职责分开。案例三反例为:
//反例
var creatLogin=(function(){
    var div;
    return function(){
        if(!div){
            div=document.createElement('div')
            div.innerHTML='我是登录'
            div.style.display='block'
            document.body.appendChild(div)
        }
        return div;
    }
})()
  • 案例三反例改造为:
//创建登录块
var creat=function(){
    var div=document.createElement('div')
    div.innerHTML='我是登录'
    div.style.display='block'
    document.body.appendChild(div)

    return div;
}

//创建单例
var getSingle=function(fn){
    var result;
    return function(){
        return result || (result=fn.apply(this,arguments))
    }
}

var cl=getSingle(creat);
var l1=cl()
var l2=cl()
console.log('l1===l2',l1 === l2) //true

//结合单一职责的装饰者模式的改造版本
Function.prototype.after=function(afterFn){
    var _self=this;
    return function(){
        var ret=_self.apply(this,arguments);
        afterFn.apply(this,arguments);
        return ret;
        console.log(ret)  //undefined
    }
}


var showLogin=function(){
    console.log('打开登录浮层')
}

var login=function(){
    console.log(this.getAttribute('tag'))
}

document.getElementById('button').onclick=showLogin.after(login)
//打开登录浮层
//login

针对不定参扩展运算符的使用

function a(...args){
	console.log(args)
}
a({'background-color':'yellow','font-size':200%})

a('mousedown mousemove',()=>{console.log(1)})

基于面向对象实现jquery库核心功能

  • 定义函数返还JQ对象
// function $(args){
//     return{
//         click(cb){
//             document.querySelector(args).addEventListener('click',cb)
//         }
//     }
// }


//抽象为类
//class Jq{}

function $(args){
    return new Jq(args)
    //走这个并携带这个this['prevObj']=[document]
}

  • 第一步:实现基本的$('.box1').click(()=>{console.log(111)})
// class Jq{
//     constructor(args){
//         //获取元素
//         this.ele=document.querySelector(args);
//     }
//     click(cb){
//         //绑定事件
//         this.ele.addEventListener('click',cb)
//     }
// }
  • 第二步:解决得获取多个元素的处理(解决传入的参数类型判断问题)
class Jq{
    constructor(args,root){
        //用来存放(上一个)操作的节点
        //默认document
        //root为上次操作得节点
        if(typeof root==='undefined'){
            this['prevObj']=[document]
        }else{
            this['prevObj']=root
        }
        
                /**
         * 判断传入的参数的类型
         * 
         * 如果只写if不写else有bug
         */

        /**'DOMContentLoaded'
          * 当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完全加载。
          */
        if(typeof args === 'string'){
            /**
             *1.id类名 标签名
                $('.box1').click(()=>{
                console.log(111)
                })
             */
            let eles=document.querySelectorAll(args);
            this.addEles(eles)
            // console.log('string',this)
        }else if(typeof args ==='function'){
            /**
             *2.传入回调函数
                $(function(){
                console.log('DOMContentLoaded')
                })
             */
            document.addEventListener('DOMContentLoaded',args)
        }else{
            /**
             *3.传入对象或数组
                对象
                $(document.querySelector('.box1')).click(()=>{
                    console.log(222)
                })

                数组
                $(document.querySelectorAll('div')).click(()=>{
                    console.log(222)
                })
            */
            if(args.length){
                this.addEles(args)
                // console.log(this)
            }else{
                this[0]=args
                this.length=1
                // console.log(this)
            }
        }


    }
}
  • 封装JQ的eq、click、css、on、end方法
    addEles(eles){
        /**
         * 给多个元素添加到实例化对象的this[i]上
         */
        for(var i=0;i<eles.length;i++){
            this[i]=eles[i]
        }
        this.length=eles.length || 1;
    }
    click(cb){
        //为单个元素绑定事件
        // this.ele.addEventListener('click',cb)

        //为多个元素绑定事件
        for(var i=0;i<this.length;i++){
            this[i].addEventListener('click',cb)
        }

    }

    on(...args){
        // var listenerArr=[];
        var listenerArr=args[0].split(' ');
        console.log(listenerArr)


        //先遍历循环所有的选中元素
        //在遍历循环所有的需要监听的事件
        //然后给每一个选中的元素添加监听事件
        for(var i=0;i<this.length;i++){
            for(var j=0;j<listenerArr.length;j++){
                this[i].addEventListener(listenerArr[j],args[1])
            }
        }
    }

    eq(index){
        
        // console.log(this,'xxx')


        //这是返回一个原生的js对象身上没有方法
        // return this[index]
        //return this 也不符合需求
        /**走这里得返还jq对象
         * else{
                this[0]=args
                this.length=1
                // console.log(this)
            }
         */
        //返还新的实例化对象--
        return new  Jq(this[index],this)
    }
    
    end(){
        //拿到上一次操作的节点--保存上一次操作得节点(eq)
        //this['prevObj']本身也是jq对象
        return this['prevObj']
    }

    css(...args){
        if(args.length===1){
            //从getComputedStyle返回的对象是只读的,
            //let res=$('div').css("background")
            if(typeof args[0]==='string'){
                return this.getStyle(this[0],args[0])
            }else{
                //多个元素设置多个样式
                //$("p").css({"background-color":"yellow","width":"300"});

                for(var i=0;i<this.length;i++){
                    for(var j in args[0]){
                        this.setStyle(this[i],j,args[0][j])
                    }
                }
            }
        }else{
            ////多个元素设置一个样式
            // //$("div").css("background-color","yellow");

            for(let i=0;i<this.length;i++){
                this.setStyle(this[i],args[0],args[1])
            }
        }
    }

    getStyle(ele,styleName){
        return window.getComputedStyle(ele,null).getPropertyValue[styleName]
    }

    setStyle(ele,styleName,styleValue){
        ele.style[styleName]=styleValue
    }
    
    animate(...args){
    /**
     * $("button").on("click",function(){
            $("div").animate({width:"300px",height:'200px'});
        })
     */
    // console.log(this)
    // console.log(args) [{width: "300px"}] j=0

    for(var i=0;i<this.length;i++){
       if(typeof args[args.length-1]==="function"){
              this[i].addEventListener("transitionend",args[args.length-1])
          }
        for(var j in args[0]){
            // console.log(j)
            //原本的位置console.log(this[i].getBoundingClientRect()[j])
            //现在的位置args[0][j]
            this[i].style.transition='all 0.5s ease-out'
            this[i].style[j]=`${args[0][j]}`
        }   
    }
    // this.setStyle()
    }
  • 理解模拟实现jquery的$.cssHooks功能
  • 立足未来的思想:后期如果出现没有收录的新css属性,用户可以自定义拓展该行为
  • 这里拓展的新属性为'hw',该属性的功能是可以获取元素的宽和高,可以设置元素的宽和高
$.cssHooks['hw'] = {
    get(ele){
        console.log("get...");
        return ele.offsetWidth + " " + ele.offsetHeight;
    },
    set(ele,newValue){
        console.log("set...");
        ele.style.width = newValue;
        ele.style.height = newValue;
    }
}

//调用方式:
$("div").css("hw","200px")

css(...args){
    if(args.length===1){
        if(typeof args[0] === "string"){
            if(args[0] in $.cssHooks){
              return  $.cssHooks[args[0]].get(this[0]);
            }

            // 获取样式
            return this.getStyle(this[0],args[0]);
        }else{
            // 对象  多个元素设置 多个样式
            for(let i=0;i<this.length;i++){
                for(let j in args[0]){
                    this.setStyle(this[i],j,args[0][j]);
                }
            }
        }
    }else{
        // css("background","yellow");
        // 设置样式;多个元素设置一个样式
        for(let i=0;i<this.length;i++){
            this.setStyle(this[i],args[0],args[1]);
        }
    }
}



setStyle(ele,styleName,styleValue){
    if(typeof styleValue === "number"){
        //如果该属性不在$.cssNumber上就需要加上单位'px'
        if(!$.cssNumber[styleName]){
            styleValue = styleValue + "px";
        }
    }
    //如果该属性在拓展的$.cssHooks上需要调用set方法
    if(styleName in $.cssHooks){
        $.cssHooks[styleName].set(ele,styleValue);
    }
    //否则就常规化处理
    ele.style[styleName] = styleValue;
}