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

  • 相关阅读:
    UVA 1513
    《ArcGIS Runtime SDK for Android开发笔记》——问题集:.geodatabase创建,创建时内容缺失问题总结
    《ArcGIS Runtime SDK for Android开发笔记》——问题集:使用TextSymbol做标注显示乱码
    《ArcGIS Runtime SDK for Android开发笔记》——(7)、示例代码arcgis-runtime-samples-android的使用
    《ArcGIS Runtime SDK for Android开发笔记》——(6)、基于Android Studio的ArcGIS Android工程结构解析
    《ArcGIS Runtime SDK for Android开发笔记》——(5)、基于Android Studio构建ArcGIS Android开发环境(离线部署)
    《ArcGIS Runtime SDK for Android开发笔记》——(4)、基于Android Studio构建ArcGIS Android开发环境
    《ArcGIS Runtime SDK for Android开发笔记》——(3)、ArcGIS Runtime SDK概述
    《ArcGIS Runtime SDK for Android开发笔记》——(2)、Android Studio基本配置与使用
    《ArcGIS Runtime SDK for Android开发笔记》——(1)、Android Studio下载与安装
  • 原文地址:https://www.cnblogs.com/kaixn/p/13643207.html
Copyright © 2011-2022 走看看