首页 > 代码库 > javascript设计模式与开发实践阅读笔记(2)—— this,闭包与高阶函数

javascript设计模式与开发实践阅读笔记(2)—— this,闭包与高阶函数

this

  this总是指向一个对象,有四种情况
1. 作为对象的方法调用。
2. 作为普通函数调用。
3. 构造器调用。
4. Function.prototype.call 或Function.prototype.apply 调用。

1. 作为对象的方法调用

当函数作为对象的方法被调用时,this 指向该对象:

var obj = {            a: 1,            getA: function(){                alert ( this === obj ); // 输出:true                alert ( this.a ); // 输出: 1            }        };
obj.getA();

2. 作为普通函数调用

当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this总是指向全局对象。在浏览器的JavaScript 里,这个全局对象是window 对象。

window.name = ‘globalName‘;
var getName = function(){
  return this.name;
};
console.log( getName() );
// 输出:globalName

特别点的例子:

1 window.name = ‘globalName‘;2 var myObject = {3   name: ‘sven‘,4   getName: function(){5     return this.name;6   }7 };8 var getName = myObject.getName;    //这里是把字面量给了getName9 console.log( getName() ); // globalName

3. 构造器调用

当用new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象,见如下代码:

1 var MyClass = function(){2     this.name = ‘sven‘;3 };4 5 var obj = new MyClass();6 alert ( obj.name ); // 输出:sven

坑:

var MyClass = function(){      this.name = ‘sven‘;      this.app="ok";      return { // 返回一个对象         name: ‘anne‘,         haha:this.app;    }};var obj = new MyClass(); console.log ( obj.name ); //  anne console.log(obj.app)    //  undefinedconsole.log(obj.haha)  //  ok    

返回一个对象时,或者说接口的时候,内部的属性就无法直接访问,除非接口当中包含,这个和封装的思路是一样的。

4. Function.prototype.call 或Function.prototype.apply 调用

可以动态地改变传入函数的this:

 1 var obj1 = { 2     name: ‘sven‘, 3     getName: function(){ 4         return this.name; 5     } 6 }; 7 var obj2 = { 8     name: ‘anne‘ 9 };10 11 console.log( obj1.getName() ); // 输出: sven12 console.log( obj1.getName.call( obj2 ) ); // 输出:anne

call 和apply的异同

同:第一个参数指定了函数体内this 对象的指向。
异:apply第二个参数为数组或者是类数组,call则是参数依次写出来,参数数量不固定

特别:如果我们传入的第一个参数为null,函数体内的this 会指向默认的宿主对象,在浏览器中则是window

 1 var obj1 = { 2     name: ‘sven‘ 3 }; 4 var obj2 = { 5     name: ‘anne‘ 6 }; 7 window.name = ‘window‘; 8 var getName = function(){ 9     alert ( this.name );10 };11 getName(); // 输出: window12 getName.call( obj1 ); // 输出: sven13 getName.call( obj2 ); // 输出: anne

call apply bind 的区别

call和apply都是直接调用,而bind返回的是一个函数,调用的话还需要加括号。

 

闭包

变量的作用域

以函数为边界,外界无法直接访问到

变量的生存周期

全局变量的生存周期是永久的,除非我们主动销毁。在函数内用var关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们都会随着函数调用的结束而被销毁。

闭包实践 阶乘函数:

1 var mult = function(){2     var a = 1;3     for ( var i = 0, l = arguments.length; i < l; i++ ){4         a = a * arguments[i];5     }6     return a;7 };

初步的函数无法记录已经计算过的阶乘,也就是说每次输入之后都需要再次计算,这里引入缓存机制,实质就是用一个对象把键值存进去

 1 var cache = {};  //存键值的对象  2  3 var mult = function(){ 4     var args = Array.prototype.join.call( arguments, ‘,‘ );  //把参数变成字符串 5  6     if ( cache[ args ] ){    //如果有这个属性,返回这个属性值 7         return cache[ args ]; 8     } 9 10     var a = 1;11     for ( var i = 0, l = arguments.length; i < l; i++ ){12         a = a * arguments[i];13     }14 15     return cache[ args ] = a;    //计算的值赋给缓存的对象16 };

这步改造实现了缓存,但是多了一个全局对象,理想状态应该所有的实现细节都放在函数内部

 1 var mult = (function(){ 2     var cache = {};   //存键值的对象  3     return function(){ 4         var args = Array.prototype.join.call( arguments, ‘,‘ );  //把参数变成字符串 5         if ( args in cache ){   //如果有这个属性,返回这个属性值 6             return cache[ args ]; 7         } 8         var a = 1; 9         for ( var i = 0, l = arguments.length; i < l; i++ ){10             a = a * arguments[i];11         }12         return cache[ args ] = a;  //计算的值赋给缓存的对象13     }14 })();

这步利用闭包缓存了计算的值,用匿名函数保护了变量,但是返回的函数暴露了很多没有必要的东西,应该把实现放在主体,再提炼一下

 1 var mult = (function(){ 2     var cache = {}; 3     var calculate = function(){ // 封闭calculate 函数,这里是计算实现部分 4         var a = 1; 5         for ( var i = 0, l = arguments.length; i < l; i++ ){ 6             a = a * arguments[i]; 7         } 8         return a; 9     }10     return function(){11         var args = Array.prototype.join.call( arguments, ‘,‘ );  //这里应该是有意暴露这个变量12         if ( args in cache ){13             return cache[ args ];14         }15         return cache[ args ] = calculate.apply( null, arguments );   //函数定义时未提供形参,这里利用apply使用函数16     }17 })();

闭包与内存管理

有种说法是闭包会引起内存泄漏,因为垃圾回收机制无法回收变量,但是使用闭包本身就是为了使用这些变量,或者留着以后使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的。
如果想回收这些变量,可以手动设为null。而由于循环引用导致的内存泄漏本质上也不是闭包的问题,解决方法也是设置变量为null。

高阶函数

满足下面任一条件就是高阶函数。
函数可以作为参数被传递 或者 函数可以作为返回值输出。

作为参数被传递,例如回调函数。
作为返回值输出,例如接受不同参数生成不同类型的判断器

例1:

 1 var Type = {}; 2     for ( var i = 0, type; type = [ ‘String‘, ‘Array‘, ‘Number‘ ][ i++ ]; ){ 3     (function( type ){ 4         Type[ ‘is‘ + type ] = function( obj ){ 5             return Object.prototype.toString.call( obj ) === ‘[object ‘+ type +‘]‘; 6         } 7     })( type ) 8 }; 9 10 Type.isArray( [] ); // 输出:true11 Type.isString( "str" ); // 输出:true

例2:既作为参数,又作为返回值

 1 var getSingle = function ( fn ) {    //传入函数作为参数 2     var ret; 3     return function () { 4         return ret || ( ret = fn.apply( this, arguments ) );  //闭包,保留ret的值,ret存在就返回ret,否则执行一次fn,并把fn的返回值赋给ret,再返回ret,即ret存储的是fn的返回值 5     }; 6 }; 7  8 var getScript = getSingle(function(){ 9     return document.createElement( ‘script‘ );10 });11 12 var script1 = getScript();13 var script2 = getScript();14 15 alert ( script1 === script2 );  //true  对象类型都是引用类型,所以两个指向的是同一个对象

这里利用闭包让ret变量不会被回收;fn只是个字面量,并没有执行,用apply的方式,可以执行fn并获得它的结果存到ret里面;所以getScript的执行结果是一个script节点,因为第一次执行后ret已经有值了,所以第二次还是返回那个值,无论执行几次,返回的对象都是同一个。这个就是单例模式的一个简单例子。

AOP是什么?

AOP(Aspect Oriented Programming,面向切面编程),其主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,再通过“动态织入”的方式掺入业务逻辑模块中。无关的功能包括日志统计、安全控制、异常处理等。

AOP的好处

可以保持业务逻辑模块的纯净和高内聚性,还可以很方便地复用日志统计等功能模块。

js实现AOP

 1 Function.prototype.before = function( beforefn ){ 2     var __self = this; // 保存原函数的引用 3     return function(){ // 返回包含了原函数和新函数的"代理"函数 4         beforefn.apply( this, arguments ); // 执行新函数,修正this,this指向window函数 5         return __self.apply( this, arguments ); // 执行原函数 6     } 7 }; 8 Function.prototype.after = function( afterfn ){ 9     var __self = this;10     return function(){11         var ret = __self.apply( this, arguments );   //这里只是另一种写法,没有本质区别12         afterfn.apply( this, arguments );13         return ret;14     }15 };16 17 var func = function(){  //定义一个func函数18     console.log( 2 );19 };20 21 func = func.before(function(){  //重新赋值func函数22     console.log( 1 );23 }).after(function(){24     console.log( 3 );25 }).before(function(){26     console.log( 0 );27 });28 29 30 func();  //执行func  0  1   2   3

这段代码实现了装饰者模式,这里在Function.prototype上添加了两个方法,因为函数都继承Function.prototype,所以所有函数都具有这两个方法;方法的内部返回了一个函数,也就是说调用完这两个方法后,返回的还是一个函数可以接着调用这两个方法。

分析这段代码需要把函数赋值和函数执行分开看。
第一步明确before和after的职责,bofore是先执行参数函数,再执行调用它的函数(本身);after是先执行调用它的函数(本身),再执行参数函数。

然后分析func这个函数。func可以拆解成这样

 1 var aa=func.before(function(){   2     console.log( 1 ); 3 }); 4  5 var bb=aa.after(function(){ 6     console.log( 3 ); 7 }); 8  9 func=bb.before(function(){10     console.log( 0 );11 });

所以func相当于

1 func=function(){2     console.log( 0 );3     bb();4 }

解析bb()

func=function(){    console.log( 0 );    aa();    console.log( 3 );}

解析aa()

func=function(){    console.log( 0 );    console.log( 1 );    console.log( 2 );  //老的func    console.log( 3 );}

所以,执行func的结果为 0 1 2 3 

 

javascript设计模式与开发实践阅读笔记(2)—— this,闭包与高阶函数