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");
//设置播放/暂停按钮默认的文本节点为►(播放按钮)
button.innerHTML = "►";
//将播放/暂停按钮放入到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,并设置播放/暂停按钮的文本节点设置为▐▐(暂停按钮)
video.addEventListener("play", function (e) {
button.innerHTML = "▐▐";
play.setAttribute("title", "Pause");
});
//设置video视频元素节点的pause暂停方法,将承载播放/暂停按钮的div容器title标题属性设置为Play,并设置播放/暂停按钮的文本节点设置为►;(播放按钮)
video.addEventListener("pause", function (e) {
button.innerHTML = "►";
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);
});