zoukankan      html  css  js  c++  java
  • JavaScript进阶教程(4)-函数内this指向解惑call(),apply(),bind()的区别

    1 函数的定义方式

    1.1 函数声明

    1.2 函数表达式

    1.3 函数声明与函数表达式的区别

    1.4 构造函数Function(了解即可,一般不用)

    2 函数的调用方式

    3 函数内 this 的指向

    4 call、apply、bind

    4.1 call,apply

    4.1.1 新的函数调用方式apply和call方法

    4.1.2 apply和call可以改变this的指向

    4.2 call,apply使用

    4.3 bind

    4.4 总结

    5 函数的其它成员(了解)

    6 高阶函数

    6.1 作为参数

    6.2 作为返回值

    7 总结


    1 函数的定义方式

    定义函数的方式有三种:

    1. 函数声明
    2. 函数表达式
    3. new Function(一般不用)

    1.1 函数声明

    1.  
      // 函数的声明
    2.  
      function fn() {
    3.  
      console.log("我是JS中的一等公民-函数!!!哈哈");
    4.  
      }
    5.  
      fn();

    1.2 函数表达式

    函数表达式就是将一个匿名函数赋值给一个变量。函数表达式必须先声明,再调用。

    1.  
      // 函数表达式
    2.  
      var fn = function() {
    3.  
      console.log("我是JS中的一等公民-函数!!!哈哈");
    4.  
      };
    5.  
      fn();

    1.3 函数声明与函数表达式的区别

    1. 函数声明必须有名字。
    2. 函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用。
    3. 函数表达式类似于变量赋值。
    4. 函数表达式可以没有名字,例如匿名函数。
    5. 函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用。

    下面是一个根据条件定义函数的例子:

    1.  
      if (true) {
    2.  
      function f () {
    3.  
      console.log(1)
    4.  
      }
    5.  
      } else {
    6.  
      function f () {
    7.  
      console.log(2)
    8.  
      }
    9.  
      }

    以上代码执行结果在不同浏览器中结果不一致。我们可以使用函数表达式解决上面的问题:

    1.  
      var f
    2.  
       
    3.  
      if (true) {
    4.  
      f = function () {
    5.  
      console.log(1)
    6.  
      }
    7.  
      } else {
    8.  
      f = function () {
    9.  
      console.log(2)
    10.  
      }
    11.  
      }

    函数声明如果放在if-else的语句中,在IE8的浏览器中会出现问题,所以为了更好的兼容性我们以后最好用函数表达式,不用函数声明的方式。

    1.4 构造函数Function(了解即可,一般不用)

    在前面的学习中我们了解到函数也是对象。注意:函数是对象,对象不一定是函数,对象中有__proto__原型,函数中有prototype原型,如果一个东西里面有prototype,又有__proto__,说明它是函数,也是对象。

    1.  
      function F1() {}
    2.  
       
    3.  
      console.dir(F1); // F1里面有prototype,又有__proto__,说明是函数,也是对象
    4.  
       
    5.  
      console.dir(Math); // Math中有__proto__,但是没有prorotype,说明Math不是函数

    对象都是由构造函数创建出来的,函数既然是对象,创建它的构造函数又是什么呢?事实上所有的函数实际上都是由Function构造函数创建出来的实例对象。

    所以我们可以使用Function构造函数创建函数。

    语法:new Function(arg1,arg2,arg3..,body);
    arg是任意参数,字符串类型的。body是函数体。

    1.  
      // 所有的函数实际上都是Function的构造函数创建出来的实例对象
    2.  
      var f1 = new Function("num1", "num2", "return num1+num2");
    3.  
      console.log(f1(10, 20));
    4.  
      console.log(f1.__proto__ == Function.prototype);
    5.  
       
    6.  
      // 所以,函数实际上也是对象
    7.  
      console.dir(f1);
    8.  
      console.dir(Function);

    2 函数的调用方式

    1. 普通函数
    2. 构造函数
    3. 对象方法
    1.  
      // 普通函数
    2.  
      function f1() {
    3.  
      console.log("我是普通函数");
    4.  
      }
    5.  
      f1();
    6.  
       
    7.  
      // 构造函数---通过new 来调用,创建对象
    8.  
      function F1() {
    9.  
      console.log("我是构造函数");
    10.  
      }
    11.  
      var f = new F1();
    12.  
       
    13.  
      // 对象的方法
    14.  
      function Person() {
    15.  
      this.play = function() {
    16.  
      console.log("我是对象中的方法");
    17.  
      };
    18.  
      }
    19.  
      var per = new Person();
    20.  
      per.play();

    3 函数内 this 的指向

    函数的调用方式决定了 this 指向的不同:

    调用方式非严格模式备注
    普通函数调用 window 严格模式下是 undefined
    构造函数调用 实例对象 原型方法中 this 也是实例对象
    对象方法调用 该方法所属对象 紧挨着的对象
    事件绑定方法 绑定事件对象  
    定时器函数 window  
    1.  
      // 普通函数
    2.  
      function f1() {
    3.  
      console.log(this); // window
    4.  
      }
    5.  
      f1();
    6.  
       
    7.  
      // 构造函数
    8.  
      function Person() {
    9.  
      console.log(this); // Person
    10.  
      // 对象的方法
    11.  
      this.sayHi = function() {
    12.  
      console.log(this); // Person
    13.  
      };
    14.  
      }
    15.  
      // 原型中的方法
    16.  
      Person.prototype.eat = function() {
    17.  
      console.log(this); // Person
    18.  
      };
    19.  
      var per = new Person();
    20.  
      console.log(per); // Person
    21.  
      per.sayHi();
    22.  
      per.eat();
    23.  
       
    24.  
      // 定时器中的this
    25.  
      setInterval(function() {
    26.  
      console.log(this); // window
    27.  
      }, 1000);

    4 call、apply、bind

    了解了函数 this 的指向之后,我们知道在一些情况下我们为了使用某种特定环境的 this 引用,需要采用一些特殊手段来处理,例如我们经常在定时器外部备份 this 引用,然后在定时器函数内部使用外部 this 的引用。
    然而实际上 JavaScript 内部已经专门为我们提供了一些函数方法,用来帮我们更优雅的处理函数内部 this 指向问题。这就是接下来我们要学习的 call、apply、bind 三个函数方法。call()、apply()、bind()这三个方法都是是用来改变this的指向的。

    4.1 call,apply

    call() 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。
    apply() 方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的参数。

    注意:call() 和 apply() 方法类似,只有一个区别,就是 call() 方法接受的是若干个参数的列表,而 apply() 方法接受的是一个包含多个参数的数组。

    call语法:

    fun.call(thisArg[, arg1[, arg2[, ...]]])
    

    call参数:

    • thisArg

      • 在 fun 函数运行时指定的 this 值
      • 如果指定了 null 或者 undefined 则内部 this 指向 window
    • arg1, arg2, ...

      • 指定的参数列表

    apply语法:

    fun.apply(thisArg, [argsArray])
    

    apply参数:

    • thisArg
    • argsArray

    apply() 与 call() 相似,不同之处在于提供参数的方式。
    apply() 使用参数数组而不是一组参数列表。例如:

    fun.apply(this, ['eat', 'bananas'])
    

    4.1.1 新的函数调用方式apply和call方法

    1.  
      function f1(x, y) {
    2.  
      console.log("结果是:" + (x + y) + this);
    3.  
      return "666";
    4.  
      }
    5.  
      f1(10, 20); // 函数的调用
    6.  
       
    7.  
      console.log("========");
    8.  
       
    9.  
      // apply和call方法也是函数的调用的方式
    10.  
      // 此时的f1实际上是当成对象来使用的,对象可以调用方法
    11.  
      // apply和call方法中如果没有传入参数,或者是传入的是null,那么调用该方法的函数对象中的this就是默认的window
    12.  
      f1.apply(null, [10, 20]);
    13.  
      f1.call(null, 10, 20);
    14.  
       
    15.  
      // apply和call都可以让函数或者方法来调用,传入参数和函数自己调用的写法不一样,但是效果是一样的
    16.  
      var result1 = f1.apply(null, [10, 20]);
    17.  
      var result2 = f1.call(null, 10, 20);
    18.  
      console.log(result1);
    19.  
      console.log(result2);

    4.1.2 apply和call可以改变this的指向

    1.  
      // 通过apply和call改变this的指向
    2.  
      function Person(name, sex) {
    3.  
      this.name = name;
    4.  
      this.sex = sex;
    5.  
      }
    6.  
      //通过原型添加方法
    7.  
      Person.prototype.sayHi = function(x, y) {
    8.  
      console.log("您好啊:" + this.name);
    9.  
      return x + y;
    10.  
      };
    11.  
      var per = new Person("小三", "男");
    12.  
      var r1 = per.sayHi(10, 20);
    13.  
       
    14.  
      console.log("==============");
    15.  
       
    16.  
      function Student(name, age) {
    17.  
      this.name = name;
    18.  
      this.age = age;
    19.  
      }
    20.  
      var stu = new Student("小舞", 18);
    21.  
      var r2 = per.sayHi.apply(stu, [10, 20]);
    22.  
      var r3 = per.sayHi.call(stu, 10, 20);
    23.  
       
    24.  
      console.log(r1);
    25.  
      console.log(r2);
    26.  
      console.log(r3);

    4.2 call,apply使用

    apply和call都可以改变this的指向。调用函数的时候,改变this的指向:

    1.  
      // 函数的调用,改变this的指向
    2.  
      function f1(x, y) {
    3.  
      console.log((x + y) + ":===>" + this);
    4.  
      return "函数的返回值";
    5.  
      }
    6.  
      //apply和call调用
    7.  
      var r1 = f1.apply(null, [1, 2]); // 此时f1中的this是window
    8.  
      console.log(r1);
    9.  
      var r2 = f1.call(null, 1, 2); // 此时f1中的this是window
    10.  
      console.log(r2);
    11.  
      console.log("=============>");
    12.  
      //改变this的指向
    13.  
      var obj = {
    14.  
      sex: "男"
    15.  
      };
    16.  
      // 本来f1函数是window对象的,但是传入obj之后,f1的this此时就是obj对象
    17.  
      var r3 = f1.apply(obj, [1, 2]); //此时f1中的this是obj
    18.  
      console.log(r3);
    19.  
      var r4 = f1.call(obj, 1, 2); //此时f1中的this是obj
    20.  
      console.log(r4);


    调用方法的时候,改变this的指向:

    1.  
      //方法改变this的指向
    2.  
      function Person(age) {
    3.  
      this.age = age;
    4.  
      }
    5.  
      Person.prototype.sayHi = function(x, y) {
    6.  
      console.log((x + y) + ":====>" + this.age); //当前实例对象
    7.  
      };
    8.  
       
    9.  
      function Student(age) {
    10.  
      this.age = age;
    11.  
      }
    12.  
      var per = new Person(10); // Person实例对象
    13.  
      var stu = new Student(100); // Student实例对象
    14.  
      // sayHi方法是per实例对象的
    15.  
      per.sayHi(10, 20);
    16.  
      per.sayHi.apply(stu, [10, 20]);
    17.  
      per.sayHi.call(stu, 10, 20);

    总结

    apply的使用语法:
    1 函数名字.apply(对象,[参数1,参数2,...]);
    2 方法名字.apply(对象,[参数1,参数2,...]);
    call的使用语法
    1 函数名字.call(对象,参数1,参数2,...);
    2 方法名字.call(对象,参数1,参数2,...);
    它们的作用都是改变this的指向,不同的地方是参数传递的方式不一样。

    如果想使用别的对象的方法,并且希望这个方法是当前对象的,就可以使用apply或者是call方法改变this的指向。

    4.3 bind

    bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也可以接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
    bind方法是复制的意思,本质是复制一个新函数,参数可以在复制的时候传进去,也可以在复制之后调用的时候传入进去。apply和call是调用的时候改变this指向,bind方法,是复制一份的时候,改变了this的指向。

    语法:

    fun.bind(thisArg[, arg1[, arg2[, ...]]])
    

    参数:

    • thisArg

      • 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
    • arg1, arg2, ...

      • 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

    返回值:

    返回由指定的this值和初始化参数改造的原函数的拷贝。

    示例1:

    1.  
      function Person(name) {
    2.  
      this.name = name;
    3.  
      }
    4.  
      Person.prototype.play = function() {
    5.  
      console.log(this + "====>" + this.name);
    6.  
      };
    7.  
       
    8.  
      function Student(name) {
    9.  
      this.name = name;
    10.  
      }
    11.  
      var per = new Person("人");
    12.  
      var stu = new Student("学生");
    13.  
      per.play();
    14.  
      // 复制了一个新的play方法
    15.  
      var ff = per.play.bind(stu);
    16.  
      ff();

    示例2:

    1.  
      //通过对象,调用方法,产生随机数
    2.  
      function ShowRandom() {
    3.  
      //1-10的随机数
    4.  
      this.number = parseInt(Math.random() * 10 + 1);
    5.  
      }
    6.  
      //添加原型方法
    7.  
      ShowRandom.prototype.show = function() {
    8.  
      //改变了定时器中的this的指向了
    9.  
      window.setTimeout(function() {
    10.  
      //本来应该是window, 现在是实例对象了
    11.  
      //显示随机数
    12.  
      console.log(this.number);
    13.  
      }.bind(this), 1000);
    14.  
      };
    15.  
      //实例对象
    16.  
      var sr = new ShowRandom();
    17.  
      //调用方法,输出随机数字
    18.  
      sr.show();

    4.4 总结

    • call 和 apply 特性一样

      • 都是用来调用函数,而且是立即调用
      • 但是可以在调用函数的同时,通过第一个参数指定函数内部 this 的指向
      • call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递即可
      • apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递
      • 如果第一个参数指定了 null 或者 undefined 则内部 this 指向 window
    • bind

      • 可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数
      • 它和 call、apply 最大的区别是:bind 不会调用
      • bind 支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递
        • 在 bind 的同时,以参数列表的形式进行传递
        • 在调用的时候,以参数列表的形式进行传递 
        • 那到底以 bind 的时候传递的参数为准呢?还是以调用的时候传递的参数为准呢?
        • 两者合并:bind 的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部。

    5 函数的其它成员(了解)

    • arguments
      • 实参集合
    • caller
      • 函数的调用者
    • length
      • 函数定义的时候形参的个数
    • name
      • 函数的名字,name属性是只读的,不能修改
    1.  
      function fn(x, y, z) {
    2.  
      console.log(fn.length) // => 形参的个数
    3.  
      console.log(arguments) // 伪数组实参参数集合
    4.  
      console.log(arguments.callee === fn) // 函数本身
    5.  
      console.log(fn.caller) // 函数的调用者
    6.  
      console.log(fn.name) // => 函数的名字
    7.  
      }
    8.  
       
    9.  
      function f() {
    10.  
      fn(10, 20, 30)
    11.  
      }
    12.  
       
    13.  
      f()

    6 高阶函数

    函数可以作为参数,也可以作为返回值。

    6.1 作为参数

    函数是可以作为参数使用,函数作为参数的时候,如果是命名函数,那么只传入命名函数的名字,没有括号。

    1.  
      function f1(fn) {
    2.  
      console.log("我是函数f1");
    3.  
      fn(); // fn是一个函数
    4.  
      }
    5.  
       
    6.  
      //传入匿名函数
    7.  
      f1(function() {
    8.  
      console.log("我是匿名函数");
    9.  
      });
    10.  
      // 传入命名函数
    11.  
      function f2() {
    12.  
      console.log("我是函数f2");
    13.  
      }
    14.  
      f1(f2);


    作为参数排序案例:

    1.  
      var arr = [1, 100, 20, 200, 40, 50, 120, 10];
    2.  
      //排序---函数作为参数使用,匿名函数作为sort方法的参数使用,此时的匿名函数中有两个参数,
    3.  
      arr.sort(function(obj1, obj2) {
    4.  
      if (obj1 > obj2) {
    5.  
      return -1;
    6.  
      } else if (obj1 == obj2) {
    7.  
      return 0;
    8.  
      } else {
    9.  
      return 1;
    10.  
      }
    11.  
      });
    12.  
      console.log(arr);

    6.2 作为返回值

    1.  
      function f1() {
    2.  
      console.log("函数f1");
    3.  
      return function() {
    4.  
      console.log("我是函数,此时作为返回值使用");
    5.  
      }
    6.  
       
    7.  
      }
    8.  
       
    9.  
      var ff = f1();
    10.  
      ff();

    作为返回值排序案例: 

    1.  
      // 排序,每个文件都有名字,大小,时间,可以按照某个属性的值进行排序
    2.  
      // 三个文件,文件有名字,大小,创建时间
    3.  
      function File(name, size, time) {
    4.  
      this.name = name; // 名字
    5.  
      this.size = size; // 大小
    6.  
      this.time = time; // 创建时间
    7.  
      }
    8.  
      var f1 = new File("jack.avi", "400M", "1999-12-12");
    9.  
      var f2 = new File("rose.avi", "600M", "2020-12-12");
    10.  
      var f3 = new File("albert.avi", "800M", "2010-12-12");
    11.  
      var arr = [f1, f2, f3];
    12.  
       
    13.  
      function fn(attr) {
    14.  
      // 函数作为返回值
    15.  
      return function getSort(obj1, obj2) {
    16.  
      if (obj1[attr] > obj2[attr]) {
    17.  
      return 1;
    18.  
      } else if (obj1[attr] == obj2[attr]) {
    19.  
      return 0;
    20.  
      } else {
    21.  
      return -1;
    22.  
      }
    23.  
      }
    24.  
      }
    25.  
      console.log("按照名字排序:**********");
    26.  
      // 按照名字排序
    27.  
      var ff = fn("name");
    28.  
      // 函数作为参数
    29.  
      arr.sort(ff);
    30.  
      for (var i = 0; i < arr.length; i++) {
    31.  
      console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
    32.  
      }
    33.  
       
    34.  
      console.log("按照大小排序:**********");
    35.  
      // 按照大小排序
    36.  
      var ff = fn("size");
    37.  
      // 函数作为参数
    38.  
      arr.sort(ff);
    39.  
      for (var i = 0; i < arr.length; i++) {
    40.  
      console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
    41.  
      }
    42.  
       
    43.  
      console.log("按照创建时间排序:**********");
    44.  
      // 按照创建时间排序
    45.  
      var ff = fn("time");
    46.  
      // 函数作为参数
    47.  
      arr.sort(ff);
    48.  
      for (var i = 0; i < arr.length; i++) {
    49.  
      console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
    50.  
      }


    m

  • 相关阅读:
    Java设计模式概述之结构型模式(装饰器模式)
    Java设计模式概述之结构型模式(代理模式)
    Java设计模式概述之结构型模式(适配器模式)
    Java设计模式概述之创建型模式
    小诀窍
    iframe的一种应用场景
    linux网络
    ANT
    Eclipse使用
    mac 安装tomcat
  • 原文地址:https://www.cnblogs.com/kaixn/p/13643207.html
Copyright © 2011-2022 走看看