2018-05-06 封装的一些小工具

debounce函数节流

函数节流通常被用于输入下拉框、滚动懒加载等场景,目的只有一个,在短时间内,限制多次调用后台接口,浪费大量资源,防止接口响应数据时间上的误差造成的页面显示数据的错误。

//设置一个timer变量,用来承接setTimeout返回的延时对象
const timer = null;
//函数节流方法
function debounce(fuc, delay) {
    //每一次进行输入或者拉动滚动条的时候,都会对延时对象进行判断,假如存在延时对象,就停止延时对象,并把延时对象置为空,释放内存,使得垃圾回收机制能更快的对延时对象进行回收
    if(timer) {
        clearTimeout(timer);
        timer = null;
    }
    //给延时对象赋予一个延时方法,fuc就是请求后台接口的函数,delay就是延时时间
    timer = setTimeout(fuc, delay);
}
//函数节流方法,只要在delay时间内,再次触发此方法,延时对象就会被停止且置空,后台的接口函数就不会被调用。
//做到了在短时间内输入或者滚动限制了多次调用后台接口,防止了接口响应数据时间上的误差造成页面数据显示的错误。

bind函数绑定

bind绑定是一种硬绑定,和apply函数、call函数一样的作用,且更加简便,其作用也是为了改变函数或者”类”函数this的指向,大多数浏览器都是已经实现了内置bind方法,我们今天来模拟一下bind方法的实现

if(Function.prototype.bind === undefined) {
    Function.prototype.bind = function(context) {
        //首先判断调用bind方法的是否是函数类型
        //假如不是函数类型,直接抛出错误异常
        if(typeof this !== "function") {
            throw new Error("bind函数必须应用在函数上面");
        }
        //获取arguments参数,除了要绑定的对象第一个参数之外的所有的参数,并生成数组
        let args = Array.prototype.slice.call(arguments, 1),
            self = this,
            fBind;
        function F() {}
        //在闭包中进行硬绑定,假如闭包不作为new绑定的函数对象使用,则其中的this指向window,假如作为new绑定的函数对象使用,则其中的this指向闭包函数对象
        fBind = function () {
            let args_fBind = Array.prototype.slice.call(arguments);
            return self.apply(this instanceof fBind ? this : context, args.concat(args_fBind));
        }
        //假如闭包作为new绑定的函数对象使用,就要继承bind绑定函数对象的显式原型
        //即闭包函数对象的隐式原型指向其bind绑定函数对象的显式原型
        F.prototype = self.prototype;
        fBind.prototype = new F();
        fBind.constructor = fBind;
        return fBind;
    }
}

window.name = "Gary";
function Person(age) {
    console.log(this.name);
    this.age = age;
    console.log(this.age);
}
//此时this默认绑定在window上面
//在这里打印:
//Gary
//24
Person(24);
let clay = {
    name: "Clay"
};
//此时进行硬绑定,this指向clay对象
//而闭包函数对象中的this则指向window
//在这里打印:
//Clay
//25
let me = Person.bind(clay);
me(25);
Person.prototype.introduce = function () {
    console.log(`name: ${this.name}, age: ${this.age}`);
};
//此时进行硬绑定,this本指向clay对象
//但是闭包函数对象却进行了new绑定构造函数操作,使得this指向闭包函数本身
//闭包函数本身并不存在name对象,所以打印this.name为undefined,无定义的
//在这里打印:
//undefined
//26
//undefined
//26
//name: undefined, age: 26
let me_bind = Person.bind(clay);
let Clay = new me_bind(26);
console.log(Clay.name);
console.log(Clay.age);
Clay.introduce();

new绑定实现构造函数对象

Javascript中存在一种特定的设计模式:原型设计模式,每一个对象都具有一个隐式原型,每一个函数对象都具有一个显式原型,通过new绑定实现构造函数对象返回的引用对象进行串联,也就是说每一个引用对象的隐式原型指向其构造函数的显式原型,所有的对象都是通过一个空对象复制出来的,也就是Object.prototype,现在我们模拟模拟一下new绑定实现构造函数对象

function ObjectNew() {
    let obj_prototype = {},
        Constructor = Array.prototype.slice.call(arguments, 0, 1)[0];
    obj_prototype.__proto__ = Constructor.prototype;
    let res = Constructor.apply(obj_prototype, Array.prototype.slice.call(arguments, 1));
    return typeof res === "object" ? res : obj_prototype;  
}

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.introduce = function () {
    console.log(`name: ${this.name}, age: ${this.age}`);
};
//这里和new绑定实现构造函数对象的效果一模一样
//在这里打印:
//Gary
//26
//name: Gary, age: 26
let gary = ObjectNew(Person, 'Gary', 26);
console.log(gary.name);
console.log(gary.age);
gary.introduce();

getElementsByClassName封装

getElementsByClassName是HTML5 DOM引入的一个获取class样式节点的方法,有一些不支持HTML5 DOM的浏览器可能不存在这个方法,所以需要对这个方法进行封装

//html code
<!Doctype html>
<html lang="zh-cn">
    <head>
        <meta charset="utf-8">
        <title>getElemetnsByClassName封装</title>
    </head>
    <body>
        <div id="home">
            <p class="number">first</p>
            <p class="number second">second</p>
            <p class="number">third</p>
        </div>
    </body>
    <script type="text/javascript" src="boxesByClassName.js"></script>
</html>

//boxesByClassName.js
function getElementsByClassName(node, className) {
    //判断DOM是否有getElementsByClassName方法,假如有,就返回node节点下的所有className子节点
    if(node.getElementsByClassName) {
        return node.getElementsByClassName(className);
    } else {
    //假如没有,就先去获取node节点下的所有子节点元素,然后根据子节点元素的className属性进行匹配外部传入的className,假如包含此className,就传入返回的数组中,最后返回数组
        let _get_className_arr = [],
            _tag_arr = node.getElementsByTagName("*"),
            _tag_arr_length = _tag_arr.length;
        for(let i = 0; i < _tag_arr_length; i++) {
            if(_tag_arr[i].className.includes(className)){
                _get_className_arr[_get_className_arr.length] = _tag_arr[i];
            }
        }
        return _get_className_arr;
    }
}

addEvent加载完成事件封装

对于window.onload事件,再熟悉不过了,用于在页面DOM全部加载完毕之后执行Javascript,防止在页面DOM渲染过程中对DOM进行操作,使得页面卡顿,降低用户体验。window.onload的挂载一个或者多个的方式于实际情况而定,所以需要对window.onlaod加载完成事件进行封装

function addEvent(eventLoad) {
    //将window.onload事件函数赋值给一个变量
    let load = window.onload;
    //判断window.onload加载完成事件上面,是否挂载了事件函数
    //假如挂载了事件函数,就将新的事件函数添加到window.onload加载完成事件队列中
    //假如没有挂载事件函数,就直接将新的事件函数挂载上去
    if (typeof load !== "function") {
        window.onload = eventLoad;
    } else {
        window.onload = function() {
            load();
            eventLoad();           
        }
    }
}

insertAfter插入到某个元素节点之后封装

parentNode.insertBefore(newElement, targetElement),insertBefore方法是在某一个元素节点之前插入新的元素节点,而DOM-Core并没有提供insertAfter方法,旨在在某一个元素节点之后插入新的元素节点,于是进行insertAfter封装

function insertAfter(newElement, targetElement) {
    //获取目标节点的父节点
    let parent = targetElement.parentNode;
    //判断父节点的最后一个子节点是否是目标节点,假如是,就直接将新的元素节点,添加到父节点下
    //假如不是,就将新的元素节点插入到目标节点兄弟节点之前
    if(parent.lastChild == targetElement) {
        parent.appendChild(newElement);
    } else {
        parent.insertBefore(newElement, targetElement.nextSibling);
    }
}

isEmpty判断对象是否为空的封装

Javascript的对象判断是否为空,并没有原生的方法,所以需要封装一下,成为自己的库

function isEmpty(obj) {
    //判断对象是否为空的标识符,默认为true,判断对象为空
    let flag = true;
    //使用ES6遍历对象,假如对象中有遍历的对象属性,就说明对象不为空,设置flag为false,判断对象不为空
    for(let [key, value] of Object.entries(obj)) {
        if(obj.hasOwnProperty(key)) {
            flag = false;
            break;
        }
    }
    return flag;
}

getNextElementSibling获取元素节点的下一个兄弟元素节点

node.nextSibling是获取某一个元素节点的下一个兄弟节点,但是节点类型可能是元素、属性以及文本,是不确定的,而通常,我们希望获取下一个兄弟节点的时候为元素节点,所以需要对获取元素节点的下一个兄弟元素节点进行封装

function getNextElementSibling(node) {
    //判断元素节点是否有下一个兄弟节点,如果没有返回false
    if(!node.nextSibling) return false;
    //获取元素节点的下一个兄弟节点
    let nextSibling = node.nextSibling;
    //假如元素节点的下一个兄弟节点的节点类型为1,也就是元素节点,就返回元素节点的下一个兄弟节点
    if(nextSibling.nodeType === 1) {
        return nextSibling;
    }
    //假如元素节点的下一个兄弟节点的节点类型为除了1以外的其他数字,就再次递归元素节点的下一个兄弟节点,直到找到下一个兄弟元素节点位置
    return getNextElementSibling(nextSibling);   
}

moveElement动态的元素节点

通过原生的Javascript来生成动画,是解决CSS3不兼容低版本浏览器的最好的平稳退化方案,利用setTimeout生成的通用动画效果的函数方法在此

//设置一个承接时间对象的变量
let timer = null;
//函数传递四个参数,要产生移动的元素节点,将要移动到的x轴位置,将要移动到的y轴位置,以及移动元素节点的时间(单位: 毫秒)
function moveElement(node, x, y, speed) {
    //首先校验是否存在移动元素的节点,假如不存在,直接返回false,退出函数
    if(!document.getElementById(node)) return false;
    let elem = document.getElementById(node);
    //然后判断是否有时间对象,假如有的话,直接清理掉,并把时间对象置为空,以便浏览器垃圾回收机制更快的回收内存中的不需要的数据
    if(timer) {
        clearTimeout(timer);
        timer = null;
    }
    //判断移动元素节点样式的top和left属性是否存在,如果不存在,初始化top和left的属性为0px
    if(!elem.style.top) {
        elem.style.top = "0px";
    }
    if(!elem.style.left) {
        elem.style.left = "0px";
    }
    //获取移动元素节点样式的top和left属性值,并转化为绝对值的数值类型
    let xpo = Math.abs(parseInt(elem.style.left)),
        ypo = Math.abs(parseInt(elem.style.top));
    //假如移动元素节点样式的left的绝对值与要移动到的x轴位置相等,且移动元素节点样式的top的绝对值与要移动到的y轴位置相等,则返回true,退出递归函数
    if(xpo === x && ypo === y) {
        return true
    }
    let dist;
    //假如移动元素节点样式的left的绝对值小于要移动到的x轴位置,就将移动元素节点样式的left的绝对值赋值为它俩的差的十分之一的向上取整数值,这样的目的是为了实现越接近目标位置速度越慢的动态效果
    if(xpo < x) {
        dist = Math.ceil((x - xpo) / 10);
        xpo += dist;
    }
    //假如移动元素节点样式的left的绝对值大于要移动到的x轴位置...
    if(xpo > x) {
        dist = Math.ceil((xpo - x) / 10);
        xpo -= dist;
    }
    //假如移动元素节点样式的top的绝对值小于要移动到的y轴位置,就将移动元素节点样式的top的绝对值赋值为它俩的差的十分之一的向上取整数值,这样的目的是为了实现越接近目标位置速度越慢的动态效果
    if(ypo < y) {
        dist = Math.ceil((y - ypo) / 10);
        ypo += dist;
    }
    //假如移动元素节点样式的top的绝对值大于要移动到的y轴位置...
    if(ypo > y) {
        dist = Math.ceil((ypo - y) / 10);
        ypo -= dist;
    }
    //将移动元素节点样式的top和left的绝对值取反,赋值重新赋值给移动元素节点样式
    elem.style.left = `${-xpo}px`;
    elem.style.top = `${-ypo}px`;
    //未到目标位置,设置时间处理函数继续递归动态的元素节点函数,并把时间对象赋值给timer变量
    timer = setTimeout(function timer() {
        moveElement(node, x, y, speed);
    }, speed);
}

getGreyGeneral根据图片利用canvas制作灰度图

利用canvas画布可以制作各种各样的图片以及动画效果,包括二维、三维等图片和动画,灰度图就是二维图片中一个比较难处理的例子,由此对其进行封装

function getGreyGeneral(img) {
    //假如Modernizr检测兼容的全局对象中查找不到canvas属性,就返回false,退出函数
    if(!Modernizr.canvas) return false;
    //假如document对象中不存在createElement方法,也返回false,退出函数
    if(!document.createElement) return false;
    //创建一个canvas元素节点对象
    let canvas = document.createElement("canvas");
    //设置canvas元素节点对象的宽和高为图片的宽和高
    canvas.width = img.width;
    canvas.height = img.height;
    //获取canvas二维画布绘图环境
    let ctx = canvas.getContext("2d");
    //在二维画布绘图环境上面,根据图片DOM对象进行绘制,从图片DOM对象坐标的(0, 0)位置开始绘制
    ctx.drawImage(img, 0, 0);
    //获取到二维绘图环境的图片数据,从坐标(0, 0)位置开始,到图片的(img.width, img.height)宽度和高度位置截止
    let c = ctx.getImageData(0, 0, img.width, img.height);
    //对图片数据进行灰度处理,获取到每个像素的rgb值,每四个像素(最后一个像素永远是255)数据一组,求rgb的平均值,并重新赋值给rgb 
    for(let i = 0;i < c.height;i ++) {
        for(let j = 0;j < c.width;j ++) {
            let x = i * 4 * c.height + j * 4,
                data = c.data,
                r = data[x],
                g = data[x + 1],
                b = data[x + 2];
            data[x] = data[x + 1] = data[x + 2] = (r + g + b) / 3;
        }
    }
    //将灰度处理好的二维绘图环境图片数据,重新赋予到原来的二维绘图环境上面,且设置二维绘图环境的偏移位置(0, 0),以及绘图处理渲染的偏移位置(0, 0),最后设置二维绘图环境图片数据的宽度和高度
    ctx.putImageData(c, 0, 0, 0, 0, img.width, img.height);
    //返回canvas生成的图片数据URL
    return canvas.toDataURL();
}

setVideoControl配置video视频播放器的默认播放和暂停按键

HTML5中新加了video和audio标签,以前都是使用object和embed标签来插入视频和音频插件,有好多繁琐的配置,现在有了video和audio标签使得我们在编写视频和音频代码时,方便快捷了很多,下面是对video播放和暂停键重置的封装

function setVideoControl(video) {
    //首先先将video视频元素节点的controls属性删掉,这样controls一系列默认的播放、暂停键以及滑动条等配置就被剔除掉了
    video.removeAttribute("controls");
    //设置video视频元素节点的宽度和高度,分别为视频的实际宽度和高度
    video.width = video.videoWidth;
    video.height = video.videoHeight;
    //设置video视频元素节点的宽度和高度,分别也为视频的实际宽度和高度
    video.parentNode.width = video.videoWidth;
    video.parentNode.height = video.videoHeight;
    //创建一个承接播放/暂停按钮的div容器,再创建一个播放/暂停按钮
    let play = document.createElement("div"),
        button = document.createElement("button");
    //设置div容器的title标题属性默认为"Play"
    play.setAttribute("title", "Play");
    //设置播放/暂停按钮默认的文本节点为&#x25BA;(播放按钮)
    button.innerHTML = "&#x25BA;";
    //将播放/暂停按钮放入到div容器中
    play.appendChild(button);
    //并将承接播放/暂停按钮的div容器插入到video视频元素节点之前
    video.parentNode.insertBefore(play, video);
    //设置承接播放/暂停按钮的div容器点击事件: 
    //假如video视频元素节点的暂停属性为true,则执行video视频元素节点的play播放方法
    //否则执行vedio视频元素节点的pause暂停方法
    play.onclick = function (e){
        if(video.paused) {
            video.play();
        } else {
            video.pause();
        }
    };
    //设置video视频元素节点的play播放方法,将承载播放/暂停按钮的div容器title标题属性设置为Pause,并设置播放/暂停按钮的文本节点设置为&#x2590;&#x2590;(暂停按钮)
    video.addEventListener("play", function (e) {
        button.innerHTML = "&#x2590;&#x2590;";
        play.setAttribute("title", "Pause");
    });
    //设置video视频元素节点的pause暂停方法,将承载播放/暂停按钮的div容器title标题属性设置为Play,并设置播放/暂停按钮的文本节点设置为&#x25BA;;(播放按钮)
    video.addEventListener("pause", function (e) {
        button.innerHTML = "&#x25BA";
        play.setAttribute("title", "Play");
    });
    //设置video视频元素节点的ended视频结束方法,将video视频元素节点的currentTime属性(当前滑动条进度)设置为:起始位置,并执行video视频元素节点pause暂停方法
    video.addEventListener("ended", function (e) {
        video.currentTime = 0;
        video.pause();
    });
}

Symbol.iterator迭代对象

说起Symbol.iterator,就会想到ES6新引进的迭代器,像Array.keys(),Array.values(),Array.entries(),都是Symbol.iterator迭代对象实现的,每一次迭代(.next())都会返回一个对象: {value: 迭代的值, done: 迭代的状态},当迭代结束,就会返回{value: undefined, done: true},但是这个迭代对象只存在于数组、Object对象以及Generator等迭代器里,要想迭代对象,需要手动去添加它的Symbol.iterator方法

let yinwk = {
    name: "Gary",
    age: 25,
    hobby: ["basketball", "tennis"],
    [Symbol.iterator]() {
        //首先将this对象赋值给一个变量
        let that = this,
            //然后获取this对象指向的本对象的属性数组
            that_key = Object.keys(that),
            //接着获取本对象的属性数组的长度
            len = that_key.length,
            //最后定义一个递增变量,用来遍历本对象的属性值
            index = 0;
        //返回一个对象,对象中有一个next函数方法,在其中判断递增变量index是否大于属性数组的长度,
        //假如不大于,说明没有迭代完成,则返回{value: 属性值(that[that_key[index++]]), done: false}
        //假如大于,说明迭代完成,则返回{value: 属性值(that[that_key[index++]]), done: true}     
        return {
            next() {
                if(index > len){
                    return {
                        value: that[that_key[index++]],
                        done: true
                    }
                } else {
                    return {
                        value: that[that_key[index++]],
                        done: false
                    }
                }
            }
        }
    }
};
//对添加好迭代对象的对象进行遍历
//这里打印:
// "Gary"
// 25
// ["basketball", "tennis"]
for(let value of yinwk) {
    console.log(value);
}

闭包实现缓存机制

有时一些函数内的重复循环、判断甚至是普通的同步定义赋值,对于浏览器来说或大或小都是损耗,现在我们使用闭包对重复的操作缓存起来,减少重复的操作和损耗,提高浏览器的性能

function cache() {
    //进行存储的对象
    let res_obj = {};

    //进行运算的操作函数
    function operation() {
        let a = 1;
        for(let i = 0; i < arguments.length; i++) {
            a *= arguments[i];
        }
        return a;
    }

    return function() {
        let args = Array.prototype.join.call(arguments, "_");
        if(res_obj[args]) {
            return res_obj[args];
        }
        console.log("first cache~");
        return res_obj[args] = operation.apply(this, arguments);
    }
}

let cache_control = cache();
//在这里打印:
//first cache~
let cache_result = cache_control(3, 4, 5);
//在这里打印:
//60
console.log(cache_result);
//在这里打印:
//60
console.log(cache_control(3, 4, 5));

使用命令模式实现模拟的开关Tv的功能

将开关Tv对象封装至函数内部,以构造函数的引用对象或者闭包函数的方式导出,调用新的函数方法控制模拟开关Tv,首先以构造函数的引用对象方式导出

let Tv = {
    open() {
        console.log("open the Tv");
    },
    close() {
        console.log("close the Tv");
    }
};

//以构造函数的引用对象方式导出
function CommandInOrder(command) {
   this.command = command;
}

//原型链函数方法: 打开Tv
CommandInOrder.prototype.openTheTv = function() {
    this.command.open();
};

//原型链函数方法: 关闭Tv
CommandInOrder.prototype.closeTheTv = function() {
    this.command.close();
};

function CommandInOrderOutput(command) {
    document.getElementById("open").onclick = function(e) {
        command.openTheTv();
        //取消冒泡
        e.stopImmediatePropagation();
    };

    document.getElementById("close").onclick = function(e) {
        command.closeTheTv();
        //取消冒泡
        e.stopImmediatePropagation();
    }
}

//点击id为open的元素
//在这里打印:
//open the Tv
//点击id为close的元素
//在这里打印:
//close the Tv
CommandInOrderOutput(new CommandInOrder(Tv));

接着是以闭包的方式导出

let Tv = {
    open() {
        console.log("open the Tv");
    },
    close() {
        console.log("close the Tv");
    }
};

//以闭包的方式导出
function CommandInOrder(command) {
    function openTheTv() {
        command.open();
    }
    function closeTheTv() {
        command.close();
    }
    return {
        openTheTv,
        closeTheTv
    }
}

function CommandInOrderOutput(command) {
    document.getElementById("open").onclick = function(e) {
        command.openTheTv();
        //取消冒泡
        e.stopImmediatePropagation();
    };
    document.getElementById("close").onclick = function(e) {
        command.closeTheTv();
        //取消冒泡
        e.stopImmediatePropagation();
    };
}

//点击id为open的元素
//在这里打印:
//open the Tv
//点击id为close的元素
//在这里打印:
//close the Tv
CommandInOrderOutput(CommandInOrder(Tv));

高阶函数

回调高阶函数事例: xhr请求回调高阶函数

//xhr请求回调高阶函数
function xhr_request(callback) {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", "../data/code.json");
    xhr.onreadystatechange = function () {
        callback.apply(xhr, arguments);
    };
    xhr.send(null);
}

//在这里打印:
//{code: "000000"}
xhr_request(function (e) {
    if(this.readyState === 4) {
        if(this.status === 200) {
            console.log(JSON.parse(this.responseText));
        }
    }
    //取消冒泡
    e.stopImmediatePropagation();
});

回调函数高阶函数事例: 创建DOM节点回调高阶函数

//创建DOM节点回调高阶函数
function createDOMNode(callback) {
    for(let i = 0; i < 10; i++) {
        let div = document.createElement("div");
        callback.call(div, i);
        document.body.appendChild(div);
    }
}

createDOMNode(function (number) {
    let text = document.createTextNode(number);
    this.appendChild(text);
});

闭包高阶函数: 单例模式

//单例函数
function getSingle(func) {
    let res;
    return function() {
        return res || (res = func.apply(this, arguments));
    }
}

let Single = getSingle(function () {
    return document.createElement("script");
});

let single_one = Single();
let single_two = Single();
//在这里打印:
//true
console.log(single_one === single_two);

闭包高阶函数: AOP装饰者模式

/**
 * 在某个函数之前执行
 */
Function.prototype.before = function(before) {
    let self = this;
    return function() {
        before.apply(this, arguments);
        return self.apply(this, arguments);
    }
};

/**
 * 在某个函数之后执行
 */
Function.prototype.after = function(after) {
    let self = this;
    return function() {
        let res_func = self.apply(this, arguments);
        after.apply(this, arguments);
        return res_func;
    }  
};

function mid() {
    console.log("this is middle~");
}

//AOP装饰者模式完成并执行
let res = mid.before(function() {
    console.log("this is before~");
}).after(function() {
    console.log("this is after~");
});

//在这里打印:
//this is before~
//this is middle~
//this is after~
res();

高阶函数实用

curry化,柯里化高阶函数

/**
 * 柯里化高阶函数
 */
function Currying() {
    let res_arr = [];
    function curry_result() {
        let m = 0;
        for(let i = 0; i < arguments.length; i++) {
            m += arguments[i];
        }
        return m;
    }
    return function() {
        if(arguments.length > 0) {
            let args = Array.prototype.slice.call(arguments, 0);
            res_arr = [...res_arr, ...args];
        } else {
            return curry_result.apply(this, ...res_arr);
        }
    }
}

let currying_res = Currying();
currying_res(300);
currying_res(255);
currying_res(244);
currying_res(344);
currying_res(88);
//在这里打印:
//1231
console.log(currying_res());

uncurry化,非柯里化高阶函数,直接将原型函数方法外用

/**
 * 非柯里化高阶函数
 */
Function.prototype.uncurrying = function() {
    let self = this;
    return function() {
        let obj = Array.prototype.slice.call(arguments, 0, 1)[0],
            args = Array.prototype.slice.call(arguments, 1);
        return self.apply(obj, args);    
    }
};

let push = Array.prototype.push.uncurrying();
//在这里打印:
//[1, 2, 3, 4]
(function() {
    push(arguments, 4);
    console.log(arguments);
})(1, 2, 3);

let obj = {
    length: 3,
    0: 1,
    1: 2,
    2: 3
};
push(obj, "4");
//在这里打印:
//{0: 1, 1: 2, 2: 3, 3: "4"}
console.log(obj);

uncurry化拓展,非柯里化高阶函数

/**
 * 非柯里化高阶函数
 */
Function.prototype.uncurrying = function() {
    let self = this;
    return function() {
        let obj = Array.prototype.slice.call(arguments, 0, 1)[0],
            args = Array.prototype.slice.call(arguments, 1);
        return self.apply(obj, args);    
    }
};

let method_arr = ["push", "shift", "forEach"];
for(let i = 0; i < method_arr.length; i ++) {
    Array[method_arr[i]] = Array.prototype[method_arr[i]].uncurrying();
}
let obj = {
    length: 3,
    0: 1,
    1: 2,
    2: 3
};
Array.push(obj, "44");
//在这里打印:
//{0: 1, 1: 2, 2: 3, 3: "44", length: 4}
console.log(obj);
Array.shift(obj);
//在这里打印:
//{0: 2, 1: 3, 2: "44", length: 3}
console.log(obj);
//在这里打印:
//2
//3
//"44"
Array.forEach(obj, function(item, index){
    console.log(item);
});

uncurry化拓展call与apply,非柯里化高阶函数

/**
 * 非柯里化高阶函数
 */
Function.prototype.uncurrying = function() {
    let self = this;
    return function() {
        let obj = Array.prototype.slice.call(arguments, 0, 1)[0],
            args = Array.prototype.slice.call(arguments, 1);
        return self.apply(obj, args);    
    }
};

let call = Function.prototype.call.uncurrying();
let name = "Gary";
window.name = "Clay";
function introduce() {
    console.log(name);
    console.log(this.name);
}
//在这里打印:
//"Gary"
//"Clay"
call(introduce, window);

let apply = Function.prototype.apply.uncurrying();
function Gary_myself() {
    console.log(arguments);
    console.log(this.name);
}
//在这里打印:
//["simon"]
//"wenkai yin"
apply(Gary_myself, {name: "wenkai yin"}, ["simon"]);

函数节流高阶函数,我们在前面整理过函数节流,只是这次的函数节流与上次不同,这次在延时的时间内不能再执行调用方法的操作,只有等到延时处理器执行完才停止延时处理器,而上次无论是是否是在延时的时间内,只要再次执行调用方法的操作,就直接停止延时处理器

/**
 * 函数节流
 */
function throttle(func, speed) {
    let first_time = true,
        timer;
    return function() {
        let self = this,
            args = arguments;
        if(first_time) {
            func.apply(self, args);
            first_time = false;
        }

        if(timer) {
            return false;
        }

        timer = setTimeout(function time() {
            clearTimeout(timer);
            timer = null;
            func.apply(self, args);   
        }, speed);
    }
}

//在这里打印:
//Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
//Event {isTrusted: true, type: "resize", target: Window, currentTarget: Window, eventPhase: 2, …}
//800
window.onresize = throttle(function(e) {
    console.log(this);
    console.log(e);
    console.log(800);
}, 800);

分时高阶函数,有时为了防止大量的DOM操作,比如一次性创建几万个DOM节点,一起添加到页面上,使得浏览器卡顿甚至是卡down,分时函数就是解决这个问题的比较不错的方法,将这几万个DOM节点,分为很多份,在一定的时间内,只去处理一份内的节点数据,添加到页面上,这样比较大的优化了浏览器的性能

/**
 * 分时函数
 */
function timeShare(share_arr, func, count) {
    let timer,
        inner_arr = share_arr;
    function resolve_res() {
        for(let i = 1; i <= Math.min(count || 1, inner_arr.length); i ++) {
            let inner_shift = inner_arr.shift();
            func.call(this, inner_shift);
        }
    }
    return function() {
        let self = this,
            args = arguments;
        timer = setInterval(function time() {
            if(inner_arr.length <= 0) {
                clearInterval(timer);
                timer = null;
            }
            resolve_res.apply(self, args);
        }, 100);
    }
}

let share_arr = Array.from(Array.apply(null, {length: 1000}), function map(item, index) {
    return index + 1;
});
timeShare(share_arr, function(i) {
    let div = document.createElement("div"),
        text = document.createTextNode(i);
    div.appendChild(text);
    document.body.appendChild(div);    
}, 8);

惰性加载高阶函数

/**
 * 普通惰性加载函数,调用时,每一次都要去判断哪种事件方式浏览器可用,这样虽对于性能没有什么大影响,但是还是很不优雅的
 */
function addEvent(elem, type, func) {
    if(window.addEventListener) {
        elem.addEventListener(type, function() {
            func.apply(this, arguments);
        });
    }
    if(window.attachEvent) {
        elem.attachEvent(`on${type}`, function() {
            func.apply(this, arguments);
        });
    }
}
addEvent(document.getElementById("open"), "click", function(e) {
    console.log(e.target);
});

/**
 * 这样省去了在调用时的判断哪种事件方式浏览器可用,换成了在浏览器加载时判断,只判断一次,这样在调用时就可以直接使用判断后的事件方式了,但是假如页面没有使用到事件,还是进行了判断哪种事件方式浏览器可用,这样浪费了浏览器的性能,还是不能这样做的
 */
let addEvent = (function (){
    if(window.addEventListener) {
        return function(elem, type, func) {
            elem.addEventListener(type, function() {
                func.apply(this, arguments);
            });
        }
    }
    if(window.attachEvent) {
        return function(elem, type, func) {
            elem.attachEvent(`on${type}`, function() {
                func.apply(this, arguments);
            });
        }
    }
})();
addEvent(document.getElementById("open"), "click", function(e) {
    console.log(e.target);
});

/**
 * 这样只有在第一次调用addEvent函数方法的时候,才回去判断哪种事件方式浏览器可用,在第一次调用之后,就直接将判断后的那种事件方式以另外一种函数方法的方式赋值给addEvent变量,然后再继续执行调用addEvent函数方法,再一次调用时,就不需要判断哪种方式浏览器可用了,直接调用第一次判断过后赋值给addEvent变量的函数方法即可,这就是惰性加载函数
 */
let addEvent = function(elem, type, func) {
    let args = Array.prototype.slice.call(arguments);
    if(window.addEventListener) {
        addEvent = function(elem, type, func) {
            elem.addEventListener(type, function() {
                func.apply(this, arguments);
            });
        }
    }
    if(window.attachEvent) {
        addEvent = function(elem, type, func) {
            elem.attachEvent(`on${type}`, function() {
                func.apply(this, arguments);
            });
        }
    }
    addEvent(elem, type, func);
};
addEvent(document.getElementById("open"), "click", function(e) {
    console.log(e.target);
});