函数的四种调用模式

概念

  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. [BZOJ2243][SDOI2011]染色 解题报告|树链剖分

    Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“ ...

  2. [POJ2954&POJ1265]皮克定理的应用两例

    皮克定理: 在一个多边形中.用I表示多边形内部的点数,E来表示多边形边上的点数,S表示多边形的面积. 满足:S:=I+E/2-1; 解决这一类题可能运用到的: 求E,一条边(x1,y1,x2,y2)上 ...

  3. 转:Python网页解析:BeautifulSoup vs lxml.html

    转自:http://www.cnblogs.com/rzhang/archive/2011/12/29/python-html-parsing.html Python里常用的网页解析库有Beautif ...

  4. tmux下vim颜色不正常问题

    在解决了tmux下,make menuconfig颜色不正常问题https://www.cnblogs.com/zqb-all/p/9702582.html后,引入了新的问题,vim颜色错乱. 尝试了 ...

  5. Mac-item+zsh

    $brew cask install iterm2 $ sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/m ...

  6. jquery发送json请求,给springmvc接收

    js var obj = { 'name':name, 'desc':desc, 'scheduleStartTime':scheduleStartTime, 'scheduleEndTime':sc ...

  7. 1.flume概述

    我们的web服务器等等每天会产生大量的日志,我们要把这些日志收集起来,移动到hadoop平台上进行分析. 那么如何移动这些数据呢?一种方法是通过shell cp到hadoop集群上,然后通过hdfs ...

  8. 在Js或者cess后加版本号 防止浏览器缓存

    在Js或者cess后加版本号 防止浏览器缓存 客户端浏览器会缓存css或js文件,从而减少加载次数,减少流量,提高网页的访问速度.为了使得每次修改js或者css能生效,可以通过改变版本号来使得客户端浏 ...

  9. nginx反向代理部署nodejs配置

    将域名abc.com反向代理到127.0.0.1:8888 upstream nodejs { server 127.0.0.1:8888; keepalive 64; } server { list ...

  10. Workman websocket 握手连接

    默认的是TCP连接方式,如果需要WebSocket,则需要更改Gateway方式, 服务端协议要和客户端协议一致才能通讯.客户端是websocket协议,服务端也要设置成websocket协议.默认为 ...