JS面向对象函数的四种调用模式

函数的四种调用模式

概念

  1. 在 js 中,无论是函数, 还是方法, 还是事件, 还是构造器,...这些东西的本质都是函数
  2. 函数, 方法, 事件, 构造器,...只是所处的位置不同
  3. 这四种模式分别是
    • 函数模式
    • 方法模式
    • 构造器模式
    • 上下文模式

函数模式

特征: 简单的函数调用, 函数名前面没有任何引导内容

    function foo(){}
    var fn = function(){};
    ...
    foo();
    fn();
    (function(){})();
    // 上面的三种都是简单的函数调用

this的含义

  1. 在函数中 this 表示全局对象
  2. 在浏览器中 this 表示 window(js 中的 global)

方法模式

特征: 方法一定是依附于一个对象, 将函数赋值给对象的一个属性, 那么就成为方法.就是函数前面必须有引导对象

    function foo(){
        this.method = function(){};
    }
    var o = {
        method : function(){}
    }

this的含义

  • 这个依附的对象(引导函数的对象)

注意点
在 arguments 这种伪数组, 或者 [] 数组这样的对象中, 这样调用函数也是方法调用, this 会只指向对象

构造器模式

构造函数在创建对象的时候, 做了些什么

  1. 使用 new 引导构造函数, 创建了一个实例对象
  2. 在创建对象的同时, 将this指向这个刚刚创建的对象
  3. 在构造函数中, 不需要 return , 会默认的 return this

分析:
由于构造函数只是给 this 添加成员, 而方法也可以完成这个操作,对与 this 来说, 构造函数和方法没有本质区别

关于return的补充, 在构造函数中

普通情况, 可以理解为构造函数已经默认进行了 return this, 添加在后面的都不会执行

  1. 如果手动的添加 return ,就相当于 return this.
  2. 如果手动的添加 return 基本类型(字符串, 数字, 布尔), 无效, 还是 return this
  3. 如果手动的添加 return null 或 return undefined, 无效, 还是 return this

特殊情况, return 对象, 最终返回对象

  • 手动添加 return 对象类型, 那么原来创建的 this 会被丢掉, 返回 return 后面的对象

上下文模式

概念: 上下文就是环境, 就是自定义this的含义

语法:

  1. 函数名.apply( 对象, [参数]);
    • 这个参数可以是数组, 也可以是伪数组
  2. 函数名.call( 对象, 参数);
    • 多个参数可以通过,进行隔离

描述:

  1. 函数名表示的是函数本身, 使用函数进行调用的时候,默认this指的是全局变量
  2. 函数名也可以是方法提供, 使用方法调用的时候, this指的是当前对象
  3. 使用 apply 或者 call 进行调用后, 无论是函数, 还是方法的 this 指向全部无效了, this 的指向由 apply 或者 call 的第一个参数决定

注意:

  1. 如果函数或方法中没有this的操作, 那么无论是哪一种函数调用模式, 其实都一样
  2. 如果是函数调用 foo(), 其实和 foo.apply(window) 类似
  3. 如果是方法调用 o.method(), 其实和 o.method.apply(o)

无论是 call 还是 apply 在没有后面参数的情况下(函数无参数, 方法无参数), 两者一致

    function foo(){
        console.log(this);  // this => window
    }
    var obj = {};
    foo.apply( obj );   // this => obj
    foo.call( obj );    // this => obj

apply 和 call 第一个参数的使用规则

  1. 如果传入的是一个对象, 就相当于设置该函数中的this为参数
  2. 如果不传参数, 或者传入 null, undefined 等,那么this就默认是 window
    foo();
    foo.apply();
    foo.apply(null);
    foo.apply(undefined);
    foo.call();
    foo.call(null);
    foo.call(undefined);
    // 上面都this都指向window
  1. 如果传入的是基本类型, 那么this指向的就是基本类型的包装类型的引用
    • number => Number
    • boolean => Boolean
    • string => String

除 this 外的其他参数

再使用上下文调用的时候, 原函数(方法)可能会带有参数, 那么要让这些参数在上下文中调用, 就需要这个第二, ( n )个参数来表示

    function foo(num){
        console.log(num);
    }
    foo.apply(null, [123]);
    // 相当于
    foo(123);

应用

上下文调用只是修改this, 但是使用最多的地方是借用函数调用

  1. 将伪数组转换为数组
    • 传统方法
        var a = {};
        a[0] = 'a';
        a[1] = 'b';
        a.length = 2;
        // 使用数组自带方法 concat();
        // 不修改原数组
        var arr = [];
        var newArr = arr.concat(a);
    

    分析
    由于 a 是伪数组, 并不是真正的数组, 不能使用数组的方法, concat 会将 a 作为一个整体 Object 加入数组
    apply 方法有一个特性, 可以将数组或者伪数组作为参数

        foo.apply( obj, 伪数组 ); // IE8 不支持
    

    将 a 作为 apply 的第二个参数

        var arr = [];
        var newArr = Array.prototype.concat.apply( arr, a);
    

    由上面的数组转换, 我们可以得到结论, 应该涉及到数组操作的方法理论上都应该可以
    push, pop, unshift, shift
    slice
    splice

  2. 让伪数组使用 push 方法
    小技巧, 伪数组添加元素
        var a = {length : 0};   // 设置伪数组的长度
        a[ a.length++   ] = 'a';
        a[ a.length++   ] = 'b';
        // 在给伪数组的元素赋值时, 同时修改伪数组的 length 属性
        // a[0] = 'a'; a.length++;
    
    伪数组使用 push 方法
        var arr = [];
        arr.push.apply( arr, a );
        // 利用 apply 可以处理伪数组的特性, 进行传参
        // 相当于 arr.push(a[0], a[1])
    
  3. 让伪数组使用 slice 方法
    数组的 slice 语法
    • arr.slice( index, endIndex ), 不包含 endIndex
    • 如果第二个参数不传参, 那么截取从 index 一直到数组结尾
    • slice 方法不会修改原数组

    通过 apply 实现伪数组使用 slice 方法

        var a = { length : 0 };
        a[a.length++] = 'a';
        a[a.length++] = 'b';
        var arr =[];
        var newArr = arr.slice.apply(a ,[0])
    

获取数组中的最大值

传统方法

    var arr = [1,2,3,4,5,6,7,8,9];
    var max = arr[0];
    for(var i = 0;i < arr.length;i++){
        if(max < arr[i]){
            max = arr[i]
        }
    }

使用 apply 借用 Math.max 获取数组中最大值
利用 apply 可以传入参数可以是数组或是伪数组的特性

    var arr = [1,2,3,4,5,6,7,8,9];
    Math.max.apply(null, arr);

创建对象的几种模式

了解了四种函数调用模式, 我们可以深一步了解创建对象的几种方式, 对象是通过构造函数创建的

  1. 工厂模式
    特点:
    1. 大量重复执行的代码, 解决重复实例化的问题
    2. 函数创建对象并返回
    3. 最典型的工厂模式就是 document.createElement()
    4. 无法知道是谁创建了这个实例对象
        function createPerson(name, age, gender){
            var o = {};
            o.name = name;
            o.age = age;
            o.gender = gender;
            return o;
        }
    
  2. 构造方法
    特点:
    1. 解决了重复实例化问题
    2. 能够知道是谁创建了这个对象(constructor)
    3. 需要通过 new 运算符穿件对象
        function Person(name,age,gender){
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
    
  3. 寄生式构造函数创建对象
    特点:
    1. 外表看起来就是构造犯法, 但本质不是通过构造方法创建对象
    2. 工厂模式 + 构造函数模式
    3. 不能确定对象的关系, 不推荐使用
        function createPerson(name,age,gender){
            var o = {};
            o.name = name;
            o.age = age;
            o.gender = gender;
            return o;
        }
        var p = new createPerson('Bob',19,'male')
    
  4. 混合式创建
    1. 构造函数 + 原型
    2. 解决了构造函数传参和共享的问题
    3. 不共享的参数使用构造函数
    4. 共享的使用原型
    5. 这种混合模式很好的解决了传参引用共享的难题
        function createPerson(name,age,gender){
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
        createPerson.prototype = {
            constructor : createPerson,
            wife : '高圆圆'
        }
    
  5. 借用构造函数继承(对象冒充)
    特点:
    1. 借用构造函数(对象冒充)只能继承构造函数的成员, 无法继承原型的成员
        function Person(name,age,gender){
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
        function Studner(name,age,gender,course){
            // 借用构造函数Person, 创建 Student 对象
            Person.call(this,name,age,gender);
            this.course = course;
        }
        var boy = new Student('Bob',19,'male',;Math);
    
  6. 寄生式继承
    特点:
    1. 原型 + 工厂模式
    2. 通过临时中转
        // 临时中转
        function person(o){
            function Foo(){};
            F.prototype = o;
            return new F();
        }
        // 寄生函数
        function create(o){
            var fn = person(o);
            fn.move = function(){};
            fn.eat = function(){};
            fn.sleep = function(){};
            return fn;
        }
        var boy = {
            name : 'Bob',
            age : 19,
            famliy : ['father','mother','wife']
        }
        var boy1 = create(boy);
        console.log(boy1.name);
        console.log(boy1.family);
        // 此时 boy1 有了 boy 的成员
    

经典例题

例题 1

    function Foo(){
        getName = function(){ alert(1); };
        return this;
    }
    function getName(){
        alert(5);
    }
    Foo.getName = function(){ alert(2); };
    Foo.prototype.getName = function(){ alert(3); };

    getName = function(){ alert(4); };

    Foo.getName();        		// 2
    getName();                	// 4
    Foo().getName();			// 4  1

    getName();         		    // 4  1
    new Foo.getName();          // 2
    new Foo().getName();      	// 3

    new new Foo().getName();    // 3

分析

  1. 预解析
  • 函数名 Foo 和函数名 getName 声明提升,函数名和函数体绑定
  1. 执行代码
  • 执行 Foo.getName();
    • 输出 2
  • 执行 getName();
    • 此时 getName 是在全局中, 未被修改, 输出4
  • Foo().getName();
    • 此事 Foo() 只是一个函数, 执行完成后 getName 被重新赋值
    • getName 因为被重新赋值为 1, 输出1
  • getName()
    • 由于 getName 被重新赋值, 所以输出 1
  • new Foo.getName();
    • Foo.getName 并未被修改
    • new 没有起任何作用
    • 输出2
  • new Foo().getName();
    • 构造函数创建了实例对象
    • 对象中没有 getName 方法, 要从对象的构造函数中的原型中寻找
    • 在 Foo.prototype 中得到 getName 输出为 3
  • new new Foo().getName();
  • 构造函数创建了实例对象
  • 对象中没有 getName 方法, 要从对象的构造函数中的原型中寻找
  • new 没有起作用
  • 在 Foo.prototype 中得到 getName 输出为 3

例题 2

var length = 10;
function fn() {
    console.log( this.length );
}

var obj = {
    length: 5,
    method: function ( fn ) {
        fn();
        arguments[ 0 ]();  // [ fn, 1, 2, 3, length: 4]
    }
};
obj.method( fn, 1, 2, 3 );

分析

  1. 预解析
    • 变量名 length, obj 和 函数名fn 声明提升, 函数名和函数体绑定
  2. 执行代码
    • length = 10, 此时 length 可以看作是 window 下的属性
    • obj = {}, 进行赋值
    • 执行 obj 中的 method 方法
    • 将 函数体 fn 进行传参
    • 跳进函数 fn,执行函数 fn(), 函数中的 this 指的是 window 下的 length, 为10
    • 明确argument是一个对象, argument[0] 指的是 fn
    • 使用对象调用函数, 这里的 this 指的是 argument 这个对象
    • 得到 argument.length 为 4
    • 最后输出结果为 10 4

例题 3

    var o = {
        method : function(){
            console.log(this);
        }
    };
    o.method();
    var f = o.method;
    f();
    (o.method)();
    var obj = {};
    (obj.fn = o.method)();
    (obj.fn)();

分析

  1. 预解析
    • 变量名 o, f ,obj 声明提升
  2. 执行函数
    • o = {},进行赋值
    • 执行o.method方法, 这里的 this 指的是 o 这个对象
    • 将 o.method 函数体,赋值给f
    • 执行 f(), 这里的 this 指的是window
    • (o.method)是一个函数表达式, 和 o.method() 的结果一致
    • obj = {}, obj进行赋值
    • o.method 的函数体, 赋值给obj.fn, 执行之后, 这里的 this 指的是window
    • (obj.fn)是一个函数表达式, 和 obj.fn() 的结果一致, tshi 指向 obj
posted @ 2016-08-08 23:12  她们都叫我剑侠  阅读(22458)  评论(0编辑  收藏  举报