• 《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 

  • 相关阅读:
    JAVA基础复习-输入、输出缓冲字节流整合(实现图片复制)
    JAVA基础复习一 字节输入、输出流整合(实现图片复制)
    JAVA基础复习-FileRead与FileWriter结合使用示例:字符输入、输出流整合(实现文本类文件的复制)
    JAVA基础复习- 字符流FileWriter
    JAVA基础复习-字符流FileRead
    JAVA基础复习一 File类的常用功能
    mysql设置大小写敏感
    Understanding the Settlement Mechanism in Microsoft Dynamics AX
    AX2012导入导出excel
    Changes in Microsoft Dynamics AX 2012 InventTrans data model
  • 原文地址:https://www.cnblogs.com/grey-zhou/p/5923199.html
Copyright © 2020-2023  润新知