《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;    //这里是把字面量给了getName
9 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)    //  undefined
console.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() ); // 输出: sven
12 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(); // 输出: window
12 getName.call( obj1 ); // 输出: sven
13 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( [] ); // 输出:true
11 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 

原文地址:https://www.cnblogs.com/grey-zhou/p/5923199.html