函数的四种调用模式

概念

  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

JS面向对象函数的四种调用模式的更多相关文章

  1. js高级-函数的四种调用模式

    1.对象方法调用模式  方法内部的this指向当前调用者的对象d 定义类 (构造函数) function Dog (dogName){ //创建一个空对象   让空对象==this this.name ...

  2. 函数的四种调用模式.上下文调用.call.apply

    闭包:函数就是一个闭包,一个封闭的作用域;         返回函数,要返回多个函数就用一个对象封装一下,         立即执行函数+return 回调函数   JS动态创建的DOM,不会被搜索引 ...

  3. js中this的四种调用模式

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...

  4. javascript中函数的四种调用模式详解

    介绍函数四种调用模式前,我们先来了解一下函数和方法的概念,其实函数和方法本质是一样,就是称呼不一样而已.函数:如果一个函数与任何对象关系,就称该函数为函数.方法:如果一个函数作为一个对象属性存在,我们 ...

  5. JS函数的四种调用模式

    函数在js中具有四种身份,分别为函数.方法.构造函数.apply或call调用 函数调用    函数调用模式中this指全局对象(window) var f1 = function() { alert ...

  6. javascript函数的四种调用模式及其this关键字的区别

    方法调用模式: 当一个函数被保存为对象的一个属性时,我们称它为一个方法.当一个方法被调用时,this被绑定到该对象. //方法调用模式 var myObject = { value: 0 , incr ...

  7. JavaScript (JS) 函数补充 (含arguments、eval()、四种调用模式)

    1. 程序异常 ① try-catch语法    测试异常 try-catch语法代码如下: try { 异常代码;     try中可以承重异常代码, console.log(“try”)  出现异 ...

  8. JS高级. 06 缓存、分析解决递归斐波那契数列、jQuery缓存、沙箱、函数的四种调用方式、call和apply修改函数调用方法

    缓存 cache 作用就是将一些常用的数据存储起来 提升性能 cdn //-----------------分析解决递归斐波那契数列<script> //定义一个缓存数组,存储已经计算出来 ...

  9. JavaScript高级之函数的四种调用形式

    主要内容 分析函数的四种调用形式 弄清楚函数中this的意义 明确构造函对象的过程 学会使用上下文调用函数 了解函数的调用过程有助于深入学习与分析JavaScript代码. 本文是JavaScript ...

随机推荐

  1. [洛谷P1040] 加分二叉树

    洛谷题目链接:加分二叉树 题目描述 设一个n个节点的二叉树tree的中序遍历为(1,2,3,-,n),其中数字1,2,3,-,n为节点编号.每个节点都有一个分数(均为正整数),记第i个节点的分数为di ...

  2. 「6月雅礼集训 2017 Day2」B

    [题目大意] 求n*n的棋盘,每行每列都有2个黑格子的方案数. n<=10^7 [题解] zzq的做法好神奇啊 行列建点,二分图 左边有i个点,右边有j个点的方案数 f[i,j] 左边有i个点, ...

  3. bzoj2811 [Apio2012]Guard

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2811 [题解] 首先我们先把没看到忍者的段去掉,可以用线段树做. 如果剩下的就是K,那么特判 ...

  4. 关于dlib人脸对比,人脸识别

    人脸检测 人脸特征点提取 人脸对比,等于两张人脸对比,识别 封装的所有识别函数,直接看下面调用就好了. # coding:utf-8 ''' 本本次封装,我主要是做两张人脸对比. 就只人脸识别部分,简 ...

  5. bzoj 1412 最小割 网络流

    比较明显的最小割建模, 因为我们需要把狼和羊分开. 那么我们连接source和每个羊,流量为inf,代表这条边不能成为最小割中的点,同理连接每个狼和汇,流量为inf,正确性同上,那么对于每个相邻的羊和 ...

  6. algorithm ch2 Merge_sort

    这是用分治法来对序列进行排序,将较长的一个序列分解为n个比较短的序列,然后分别处理这n个较小的段序列,最后合并.使用递归的来实现. 具体实现的代码如下: void MergeSort(int *A, ...

  7. Oracle基础 12 对象 objects 同义词/序列/试图/索引

    --创建同义词create public synonym employees for hr.employees;  --公共同义词需要 create public synonym 权限 表的所有用户授 ...

  8. go语言实现拷贝文件

    package main import ( "fmt" "io" "os" ) func main(){ list := os.Args / ...

  9. 【 Zabbix 】— Tomcat监控及故障重启服务

    一.监控tomcat原理 zabbix_server开启java poller,zabbix_java开启JavaGateway, 端口为:10052,tomcat JMX开启12345提供性能数据. ...

  10. java连接Fastdfs图片服务器上传失败的解决方法

    照着视频上做,但是却连接不了虚拟机linux上的图片服务器,估计是linux防火墙的问题(这个实在是神烦,前面有好几次连接不了都是因为linux防火墙),果不其然,关闭即可. Linux关闭防火墙的命 ...