Javascript函数式编程要掌握的知识点讲解
一:理解call和apply 及arguments.callee
ECMAScript3给Function的原型定义了两个方法,他们是Function.prototype.call
和 Function.prototype.apply
. 其实他们的作用是一样的,只是传递的参数不一样而已;
1. apply; 接受2个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个类似数组的集合,比如如下代码:
var yunxi = function(a,b){
console.log([a,b]); // [1,2]
console.log(this === window); // true
};
yunxi.apply(null,[1,2]);
如上代码,我们第一个参数传入null,函数体内默认会指向与宿主对象,即window对象;因此我们可以在yunxi函数内打印下值为true即可看到:
下面我们来看看使用call方法的实例如下:
var yunxi = function(a,b){
console.log([a,b]); // [1,2]
console.log(this === window); // true
};
yunxi.call(null,1,2);
可以看到 call方法的第二个参数是以逗号隔开的参数;
那么call和apply用在什么地方呢?
1.call和apply 最常见的用途是改变函数体内的this指向,如下代码:
var longen = {
name:'yunxi'
};
var longen2 = {
name: '我叫涂根华'
};
var name = "我是来测试的";
var getName = function(){
return this.name;
};
console.log(getName()); // 打印 "我是来测试的";
console.log(getName.call(longen)); // 打印 yunxi
console.log(getName.call(longen2)); // 打印 "我叫涂根华"
第一次调用 getName()
方法,因为它是普通函数调用,所以它的this指向与window,因此打印出全局对象的name的值;
第二次调用getName.call(longen);
执行这句代码后,getName这个方法的内部指针this指向于longen这个对象了,因此打印this.name
实际上是longen.name,因此返回的是name=”yunxi”;
但是this指针也有列外的情况,比如一个点击元素,当我们点击一个元素的时候,this指针就指向与那个点击元素,但是当我们在内部再包含一个函数后,在函数内再继续调用this的话,那么现在的this指针就指向了window了;比如如下代码:
document.getElementById("longen").onclick = function(){
console.log(this); // this 就指向于div元素对象了
var func = function(){
console.log(this); // 打印出window对象
}
func();
}
如上代码。可以看到外部this指向与被点击的那个元素,内部普通函数调用,this指针都是指向于window对象。但是我们可以使用call或者apply方法来改变this的指针的;如下代码:
document.getElementById("longen").onclick = function(){
console.log(this); // this 就指向于div元素对象了
var func = function(){
console.log(this); // 就指向于div元素对象了
}
func.call(this);
}
如上代码我们使用call方法调用func函数,使this指向与func这个对象了,当然上面的方法我们还可以不使用call或者apply方法来改变this的指针,我们可以在外部先使用一个变量来保存this的指针,在内部调用的时候我们可以使用哪个变量即可,如下代码演示:
document.getElementById("longen").onclick = function(){
console.log(this); // this 就指向于div元素对象了
var self = this;
var func = function(){
console.log(self); // 就指向于div元素对象了
}
func();
}
arguments.callee的理解
callee是arguments的一个属性,它可以被用作当前函数或函数体执行的环境中,或者说调用一个匿名函数;返回的是当前正在被执行的Function对象;简单的来说就是当前执行环境的函数被调用时候,arguments.callee对象会指向与自身,就是当前的那个函数的引用;
如下代码:
var count = 1;
var test = function() {
console.log(count + " -- " + (test.length == arguments.callee.length) );
// 打印出 1 -- true 2 -- true 3 -- true
if (count++ < 3) {
// 调用test()函数自身
arguments.callee();
}
};
test();
arguments.callee()
的含义是调用当前正在执行的函数自身,比如上面的test的匿名函数;
Function.prototype.bind介绍
目前很多高级浏览器都支持Function.prototype.bind
方法,该方法用来指定函数内部的this指向。为了支持各个浏览器,我们也可以自己来简单的模拟一个~
如下代码:
Function.prototype.bind = function(context) {
var self = this;
return function(){
return self.apply(context,arguments);
}
}
var yunxi = {
name: 'yunxi'
};
var func = function(){
console.log(this.name); // yunxi
}.bind(yunxi);
func();
如上代码所示:func这个函数使用调用bind这个方法,并且把对象yunxi作为参数传进去,然后bind函数使用return返回一个函数,当我们调用func()
执行这个方法的时候,其实我们就是在调用bind方法内的return返回的那个函数,在返回的那个函数内context的上下文其实就是我们以参数yunxi对象传进去的,因此this指针指向与yunxi这个对象了~ 所以打印出this.name
就是yunxi那个对象的name了;
除了上面我们看到的介绍apply或者call方法可以改变this指针外,我们还可以使用call或者apply来继承对象的方法;实质也就是改变this的指针了;
比如有如下代码:
var Yunxi = function(name){
this.name = name;
};
var Longen = function(){
Yunxi.apply(this,arguments);
};
Longen.prototype.getName = function(){
return this.name;
};
var longen = new Longen("tugenhua");
console.log(longen.getName()); // 打印出tugenhua
如上代码:我先实例化Longen这个对象,把参数传进去,之后使用Yunxi.apply(this,arguments)
这句代码来改变Longen这个对象的this的指针,使他指向了Yunxi这个对象,因此Yunxi这个对象保存了longen这个实例化对象的参数tugenhua,因此当我们调用longen.getName
这个方法的时候,我们返回this.name
,即我们可以认为返回的是Yunxi.name
因此返回的是 tugenhua,我们只是借用了下Yunxi这个对象内的this.name
来保存Longen传进去的参数而已;
二:闭包的理解
闭包的结构有如下2个特性
1.封闭性:外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外界提供访问接口;
2.持久性:一般的函数,调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调用之后,闭包结构依然保存在
系统中,闭包中的数据依然存在,从而实现对数据的持久使用。
缺点:
使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等.
如下代码:
function a(x) {
var a = x;
var b = function(){
return a;
}
return b;
}
var b = a(1);
console.log(b()); // 1
首先在a函数内定义了2个变量,1个是存储参数,另外一个是闭包结构,在闭包结构中保存着b函数内的a变量,默认情况下,当a函数调用完之后a变量会自动销毁的,但是由于闭包的影响,闭包中使用了外界的变量,因此a变量会一直保存在内存当中,因此变量a参数没有随着a函数销毁而被释放,因此引申出闭包的缺点是:过多的使用闭包会占有内存资源,或内存溢出等肯能性;
// 经典的闭包实列如下:
function f(x){ //外部函数
var a = x; // 外部函数的局部变量,并传递参数
var b = function(){ // 内部函数
return a; // 访问外部函数中的局部变量
};
a++; // 访问后,动态更新外部函数的变量
return b; // 返回内部函数
}
var c = f(5); // 调用外部函数并且赋值
console.log(c()); // 调用内部函数,返回外部函数更新后的值为6
下面我们来看看如下使用闭包的列子
在如下代码中有2个函数,f函数的功能是:把数组类型的参数中每个元素的值分别封装在闭包结构中,然后把闭包存储在一个数组中,并返回这个数组,但是在函数e中调用函数f并向其传递一个数组["a","b","c"]
,然后遍历返回函数f返回数组,我们运行打印后发现都是c undefined,那是因为在执行f函数中的循环时候,把值虽然保存在temp中,但是每次循环后temp值在不断的变化,当for循环结束后,此时temp值为c,同时i变为3,因此当调用的时候 打印出来的是temp为3,arrs[3]变为undefined;因此打印出 c undefined
解决闭包的缺陷我们可以再在外面包一层函数,每次循环的时候,把temp参数和i参数传递进去 如代码二
// 代码一
function f(x) {
var arrs = [];
for(var i = 0; i < x.length; i++) {
var temp = x[i];
arrs.push(function(){
console.log(temp + ' ' +x[i]); // c undefined
});
}
return arrs;
}
function e(){
var ar = f(["a","b","c"]);
for(var i = 0,ilen = ar.length; i < ilen; i++) {
ar[i]();
}
}
e();
// 代码二:
function f2(x) {
var arrs = [];
for(var i = 0; i < x.length; i++) {
var temp = x[i];
(function(temp,i){
arrs.push(function(){
console.log(temp + ' ' +x[i]); // c undefined
});
})(temp,i);
}
return arrs;
}
function e2(){
var ar = f2(["a","b","c"]);
for(var i = 0,ilen = ar.length; i