函数是对代码的抽象和封装,相当于丰富了语言本身
用函数把一些常用的处理代码封装起来,可以使用者不必再关注这些代码的细节,
只需要使用和依赖这个黑盒子就行了,
如果我们的项目建立在这些黑盒子上,将会降低我们项目的复杂度,所以再开开始项目的时候要尽量选择一些可靠的函数库,让他们成为你的房子的基石。
js 引擎在执行js 代码的时候,会首先创建一个全局作用域,有的叫做全局执行上下文,都他妈一样,你可以把这个全局作用域,想象成个 对象, 该对象上面预存了一些常用的方法和属性,比如
全局作用域 {
window: 自身,
Array
parseInt
......
}
等等
当遇到一些属性的访问,或者访问时候会查询该玩意
js 中除了全局作用域就只有函数作用了,后来又有了局部作用域 let 搞出来的。
在全局作用域中定义函数 function A (a, b) {}
函数A,会保存自己定义时候所在的作用域
相当于A.scop = {
全局作用域
}
当函数被调用的时候,会创建自己的作用域
A()
{
A函数作用域,该作用域有函数内声明的变量,形式参数
a,
b
arguments
等
scope : 全局作用域
}
当函数内遇到变量 c , 首先在自己作用域里面查找c ,如果没有被定义,则按照自己定义时候保存的作用域链查找上一个作用域看有没有,直到全局。
function A () {
functionB() {}
。。。。
。。。。。
B()
}
上面代码当函数A调用的时候,函数B被定义,同时保存了 函数A的作用域,
即B.scope = {
函数A的作用域
scope : {全局作用域}
}
当函数B被调用的时候,就会形成一个作用域链条
{
函数B的作用域
scope: {
函数A的作用域
scope: {
全局作用域
}
},
当执行B函数的时候,遇到一个变量,就会按照上面的作用域链条查询到全局,左后没有undefind
闭包的形成
function A () {
var c = 1;
var d = 2;
xxxx
return functionB () {}
}
var f = A ();
当 B函数在A函数中定义的时候保存了 A的作用域,最后A 函数把B retrun 出去给了变量 f;
变量f 引用了 B函数, 而B函数本身scope属性保存了A的作用域,导致A函数即使执行完了他的作用域 {
A作用域
},依然存活在堆内存中,无法销毁
但是呢该作用域,又被B的作用域保存了 {
B的作用域
scope : {
A作用域
c: 1,
b: 2
}
}
所以在f 调用的时候可以通过作用域链,肆意攀爬到A中声明的任何变量,方法,那么外层函数的作用域也就相当于了,内层存函数的一个私有小仓库,自己可以去里面存取任何东西,封装私有物品到里面去,外面任何地方访问不了,这就是闭包。所以函数可以利用闭包存储一些状态,而不是让自己是一个被调用完就完全消失过程,闭包让一个函数可以媲美对象
函数中this
通过上面的可以知道,函数其实跟调用时候的上下文没什么关系, 因为函数在定义的时候,已经通过作用链保存了自己能够访问的作用域,如果如此,那么函数就是一个封闭的,跟调用它的上下文完全没关系的一段代码,这样的函数会非常的死板,不能达到我们想要的灵活效果,
所以函数里面有了this 指针,用来跟函数跳用的上下文扯上关系,以便函数内部的代码能够处理改变调用上下文中的一些数据,
所以this 在函数中变成一个跟调用方式有关系的变量
如果一个函数作为函数调用,它内部this 指向全局对象, 如果做为对象的方法调用,那么函数认为自己里面的处理是用来针对这个对象的,所以this, 指向调用它的对象,以便方便处理对象,改变对象的状态。
有时候this,因为动态指定的关系,也会带来麻烦,应为调用的关系指到了我门不想让它指定的对象上,所以有了call 和apply
这两个方法可以将函数的this 绑定死在某个对象上,而不再根据调用方式乱指向,从而得到一个稳定完全跟调用方式没有关系的函数,所以你就不用担心你的函数会有副作用了。
箭头函数的this, 箭头函数中没有this, 当你访问箭头函数中的this的时候 其实会访问到该函数定义时候所在作用域中this.
函数可以作为参数传递的意义
函数可以作为函数参数传入,意为着你可以把一部分变化的不稳定的逻辑,让用户用函数传入,这样就可以分离业务代码中的变与不变,一个稳定的函数,却可以根据传入的回调,表现出仪态万方,增强了语言的表达力。
const appendDiv = function() { for (let i = 0; i < 100; i++) { let div = document.createElement('div'); div.innerHTML = i; document.body.appendChild(div); div.style.display = 'none' } }
该段代码,div.style.display = 'none'的逻辑硬编码在appendDiv里,显然是不合理的,使该段代码难以复用的函数,并不是每个人创建了节点之后都希望隐藏它。
所以要把这段代码抽出来,用回调函数的形式传入
const appendDiv = function(cb) { for (let i = 0; i < 100; i++) { let div = document.createElement('div'); div.innerHTML = i; document.body.appendChild(div); if(typeof cb === 'function') { cb(div); } } }; appendDiv((node) => {node.style.display = 'none'});
用回调以后,对添加到页面的节点的处理,灵活性变强,函数本身的复用性变强。
call ,apply
在js中是可以互接老婆的,如果一个对象觉的另一个对象的方法不错,那么就可以直接借来用
const obj1 = { name: 'seven' }; const obj2 { getName() { return this.name; } }; obj2.getName.call(obj1); //输出结果seven Array.prototype.push.call(arguments, 4); //借用Array.prototype上的push方法
我们的预期中,Array.prototype 上方法本来只能用来操作 数组对象,但是用call 和apply 可以把任意的对象绑定到该方法的this上,那么该方法内部根据this展开的操作,就会作用到这个绑定的对象上,跟该对象自身的方法一样,因为对象自身方法也是通过thisl来访问自身,并对自己做处理。所以借来的跟自己方法效果一摸一样。
这样也会导致函数内this泛化的问题,根据调用的方式就指向不同的对象,这样也会造成一个函数的不稳定。
高阶函数
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入...参入到业务逻辑模块中,这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次可以很方便的复用日志统计等功能模块
Function.prototype.before = function(beforefn) { let self = this; //保存函数本身 return function() { beforefn.apply(this, arguments); //执行新函数 return self.apply(this, arguments); } } Function.prototype.after = function(afterfn) { let self = this; return function() { let ret = self.apply(this, arguments); afterfn.apply(this, arguments);
return ret
} };
var fun = function() {console.log(2)};
fun = fun.before(() => console.log(1)).after(() => console.log(3));
fun();
函数柯里化 又称部分求值,一个currying的函数首先会接受一些参数,接受这些参数之后,该函数并不会立即求值,而是继续返回另外的一个函数,刚才传入的参数在函数形成的闭包中被保存起来,待到函数真正需要求值的时候,之前传入的所有参数都会被一次性用于求值
const monthlyCost = 0; //每月总花费 const cost = function(money) {monthlyCost += money}; cost(100); // 第一天 cost(200); // 第二天 cost(300); // 第三天 //。。。。 console.log(monthlyCost); const cost = (function() { let args = []; return function() { if (arguments.length === 0) { let money = 0; for (let i = 0, l = args.length; i < l; i++) { money += args[i]; } return money }else { [].push.apply(args, arguments); } } })() cost(100); cost(200); console.log(cost()); //输出值为三百
职责分离
const currying = function (fn) { let args = []; return function() { if (arguments.length === 0) { return fn.apply(this, ars) }else { [].push.appy(args, arguments); } } } const cost = (function() { let money = 0; return function() { for(let i = 0, l = arguments.length; i < l; i++) { money += arguments[i]; } return money; } })() var cos = currying(cost); cos(100); cos(200); cos(300); //以上都没真正求值 console.log(cos());
函数节流
const throttle = function(fn, interval) { let self = fn; //保存需要被延迟的函数引用 let timer; let firstTime = true; //是否为第一次 return function () { let args = arguments; let me = this; if (firstTime) { //如果为第一次调用,不需要延迟执行 self.apply(me, args); return firstTime = false; } if (timer) { return false }; timer = setTimeout(() => { clearTimeout(timer); timer = null; self.apply(me, args); }, interval || 500); } } window.onresize = throttle(function() { consle.log(1); }
分时函数
const renderFriendList = function(data) { for (let i = 0, l = data.length; i < l; i++) { let div = document.createElement('div'); div.innerHTML = i; document.body.appendChild(div); } } renderFriendList(ary); const timeChunk = function(ary, fn, count) { let obj; let t; let len = ary.length; let start = function() { for (let i = 0; i < Math.min(count || 1, ary.length); i++) { let obj = ary.shift(); fn(obj) } }; return function() { t = setInterval(function() { if (ary.length === 0) { //如果全部节点都已经被创建好 retrun clearInterval(t); start(); } }, 200) } }
let arry = []; for (let i = 1; i <= 1000; i++) { ary.push(i); } const renderFriendList = timeChunk(ary, function(n) { let div = document.createElement('div'); div.innerHTML = n; document.body.appendChild(div); }, 8);
5.惰性加载函数
let addEvent = function(elem, type, hanlder) { if (window.addEventListener) { return elem.addEventListener(type, hanlder, false); } if (window.attachEvent) { return elem.attachEvent('on' + type, hanlder); } }
该函数的缺点是每次被调用都会执行里面的分支,虽然if 分支开销不大,但是又方法可以让程序避免这些重复的执行过程
let addEvent = function(elem, type, hanlder) { if (window.addEventListener) { return function (elem, type, hanlder) { elem.addEventListener(type, hanlder, false); } } if (window.attachEvent) { return function (elem, type, hanlder) { elem.attachEvent('on' + type, hanlder); } } }