重要

JavaScript的this是有函数求值是的调用者决定的

JavaScript的this是有函数求值是的调用者决定的

JavaScript的this是有函数求值是的调用者决定的

函数中的this

函数中的this在调用时才有意义,函数的调用者决定函数中this的指向,每个函数调用时都会有this属性。

function Point2D(x, y) {
  this.x = x;
  this.y = y;
}
Point2D.prototype.showLength = function() {
  var length = Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
  console.log(length);
};
Point2D.prototype.showLengthAsync = function() {
  var self = this;   // 将this的值保存下来,传入异步函数

  setTimeout(function() {  // setTimeout是异步函数,在调用回调函数时没有指定匿名函数的this,
      self.showLength();  //因为回调函数没有指定this,非严格模式下默认指向全局对象
  }, 1000);
};

var x = 30, y = 40;
var p = new Point2D(3, 4);
var f = Point2D.prototype.showLength;

1.f():输出50,因为函数Point2D.prototype.showLength的调用者是变量f,变量fxyPonit2D函数在同一个对象中。

  • 所以函数Point2D.prototype.showLengththis指向的是包含变量fxyPonit2D函数的对象。

  • this.x = 30this.y = 40,所以f()是输出50;

        Point2D.prototype.showLength = function() {
              var length = Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
              console.log(length);
            };

2.setTimeout(p.showLength, 500); :延时500ms后输出50。函数p.showLength的调用者是异步函数setTimeoutsetTimeout不会为回调函数指定this值。

  • 在非严格模式下,p.showLength中的this默认指向全局变量

  • this.x = 30this.y = 40,所以setTimeout(p.showLength, 500)延时500ms后输出50;

  • 借用bind()方法可以将函数绑定到对象上,即将函数内的this指向bind(p)方法中传入的p对象

    setTimeout(p.showLength.bind(p), 500);    // 延时500ms后输出5,因为函数p.showLength指向对象p,p的x属性为3,y属性为4

3.p.showLengthAsync();: 延时1000ms后输出5

Point2D.prototype.showLengthAsync = function() {
  var self = this;   // 将this的值保存下来,传入异步函数

  setTimeout(function() {  // setTimeout是异步函数,在调用回调函数时没有指定匿名函数的this,
      self.showLength();  //因为回调函数没有指定this,非严格模式下默认指向全局对象
  }, 1000);
};
  • p.showLengthAsync函数的调用者是p对象本身,由于setTimeout不会为回调函数指定this值。所以在p.showLengthAsync函数中使用变量selfp.showLengthAsync函数调用的this值(即对象p)保存下来

  • 回调函数中才可以利用变量self访问到对象p,完成正确调用;

4.箭头函数

  • 箭头函数很特殊,箭头函数中没有定义this,所以可以使用外层函数的this

Point2D.prototype.showLengthAsync = function() {
  setTimeout( () => this.showLength(), 1000);   // 箭头函数没有定义this,可以访问外层函数的this
};

call()

函数的call()方法可以指定函数调用时的this。函数中调用时的this值是可以改变的

  • 并且,call()方法可以从第二个参数开始,指定传入调用函数的参数,以逗号分隔(多个原始形式的参数)

    function Point2D(x, y) {
      this.x = x;
      this.y = y;
    }
    Point2D.prototype.showLength = function() {
      var length = Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
      console.log(length);
    };
    
    var p = new Point2D(1, 1);
    console.log(p.showLength());  // ==> 1.4142...   调用p.length方法的是p对象本身,所以this.x和this.y均等于1
    
    var obj = {x: 3, y: 4};
    console.log(p.showLength.call(obj));   // ==> 5  使用函数p.showLength的call()方法改变函数调用时this的指向,
                                           //使其指向对象obj,所以this.x=3,this.y=4
    
    function foo() {
      // 使用Array.prototype.slice函数的call()方法指定函数调用时的this指向arguments对象,将其切分为数组
      var args = Array.prototype.slice.call(arguments);
    
      console.log(Array.isArray(arguments));  // false
      console.log(Array.isArray(args));   //  true
    }

call()的作用:改变当前函数调用时的this

apply()

函数的apply()方法与call()方法作用完全一致,只是在调用时传入的参数有区别:

  • call()方法:第一个参数接收新的this值,后面的参数逗号分隔,逐个排列,传入调用函数中

  • apply()方法:第一个参数接收新的this值,第二个参数接收一个数组,将数组整体作为参数传入调用函数中

    // 定义一个函数,实现一种变换,将传入其中的函数中参数的顺序颠倒
    function __reverseArgs__(fn) {
      return function() {
        var args = Array.prototype.slice.call(arguments);
        
        return fn.apply(this, args.reverse());  // 这里的this表示这个匿名函数的调用者
      };
    }
    var foo = function() {console.log(Array.from(arguments));};
    var foo2 = __reverseArgs__(foo);
    foo2(1, 2, 3, 4);
  • 注意return fn.apply(this, args.reverse());中的this。因为__reverseArgs__(fn)函数返回一个新的函数,apply()中的this指向调用这个新函数的对象。

  • foo2接收了__reverseArgs__(foo)返回的新函数,调用foo2(1, 2, 3, 4)时,因为foo2在全局对象下,所以this的值是全局对象。

  • __reverseArgs__(fn)方法应该只改变传入函数中参数的顺序,不改变原来函数调用的作用域。所以使用this将函数的调用改回传入函数的作用域。

bind()

bind()方法与call()apply()方法的最大区别在于返回值:call()apply()都会立即执行,返回结果;bind()方法会返回一个函数,并且可以通过bind()向函数中传递已经确定的参数,对于异步调用很有帮助。

function add(x, y) {
  return x + y;
}
// call()和apply()会立即执行;bind()方法会返回一个函数对象
console.log(add.call(null, 1, 2));   // ==> 3 ,使用call()方法在全局对象下对传入的参数1,2执行函数
console.log(add.apply(null, [1, 2]));   // ==> 3 apply()方法在全局对象下对传入的参数1,2执行函数
console.log(add.bind(null, 1, 2));   // ==> function () { [native code] }, 返回一个函数
// 传入两个参数
let add1 = add.bind(null, 1, 2);  // bind()方法执行时,将1和2对应传递给x,y,返回一个函数。再调用返回的函数时,不用再传递参数
console.log(add1());         //  ==> 3  为传递参数,直接调用,使用bind()时传入的参数值

// 传入一个参数
let add2 = add.bind(null, 1);   //bind()时只传递一个参数1给x,返回一个函数,在调用返回函数时只需炫迪一个参数给y即可
console.log(add2(3));    //  ==> 4  只需传递一个参数给y即可

// 不传递参数
let add3 = add.bind(null);    //调用bind()时不传递参数,返回一个函数,在调用返回函数时要传递2个参数
console.log(add3(2, 3));   // ==> 5  需要传递两个参数,与调用原函数没有区别。。。不建议使用

可以看出bind()的最大用处在于返回一个函数,并且可以向函数内传递确定的参数值(在bind()执行时时便已经确定的参数)。调用返回的函数时,无需再传入bind()时传入的参数---实现函数的部分调用

应用场景:类似于setTimeout(fn, 1000);的异步函数,需要一个函数作为参数,在1s后执行。有时对于已经知道fn调用时传入的参数值时,便可以使用bind(),先将参数传递进去,返回一个函数;等待时间到后,无需再向该函数传入参数,立即执行即可

function setBodyState(state) {
  document.body.className = state;
}
setBodyState.call(null, 'state1');   // 立即执行setBodyState函数,将document.body.className设置为'state1'。
                                    // this值为null表示在全局对象下执行该函数
setTimeout(setBodyState.bind(null, 'state2'), 1500);  // 执行bind()方法,返回一个函数作为回调函数,并且将需要向它传递
                                      //的'state2'作为参数。1500ms后立即执行返回的函数即可,无需再传入参数

bind()方法在异步函数中的应用,主要由于其返回一个函数,并且可以传入参数值的特性,可以减少异步函数调用的部分问题。

高阶函数实例

Closure中访问外部函数的argumentsthis、形参和局部变量

// __multi__()抽象一个过程,将传入的函数进行扩展,使其第一个参数接收类数组
// 调用原来的方法fn对每个第一个数组参数中的每个元素执行fn方法
function __multi__(fn) {
  return function(arrayLike, ...args) {
    return Array.from(arrayLike).map(item => fn(item, ...args));
  };
}

function __multi__(fn) {
  return function(arrayLike, ...args) {   // 返回一个函数(创建一个Closure),返回的函数接收夜歌类数组对象和rest参数作为参数
    return Array.from(arrayLike).map(function(value) {
      return fn(value, ...args);
    });
  };
}

function add(x, y) { return x + y;}
var add2 = __multi__(add);
console.log(add2([1,2,3], 4));  // ==> [5, 6, 7]

1.注意map()方法如果需要返回值,一定要在传入map()的函数中返回值,因为默认的返回值是undefined`
2.Closure中对于外层函数的局部变量、形参、实参对象argumentsthis的访问问题:

  • __multi__()返回一个函数(创建一个Closure),返回的函数保留对外层函数作用域的访问能力

  • this值和实参对象arguments是根据调用函数时确定的,如果要在Closure中访问到调用时的thisarguments对象,需要将其保存在外部函数局部作用域的变量中。

function __multi__(fn) {
  var self = this;
  var outerArgs = arguments;
  return function(arrayLike, ...args) {
    return Array.from(arrayLike).map(function(value) {
      console.log(self);    // 可以访问到外层函数的调用者
      console.log(outerArgs);  //可以访问到外层函数的实参列表,相当于访问外层函数的局部变量
      
      console.log(this);   // 访问到的this值是返回函数的调用者
      console.log(arguments);  // 访问到的arguments是传入返回函数的实参
      
      return fn(value, ...args);   // 可以访问到外部函数的形参  
    });
  };
}
  • Closure中可以访问到外部函数的形参(形参的特性与局部变量相同)

    function __multi__(fn, a = 2) {
      console.log(arguments[0]);
      return function(arrayLike, ...args) {
        return Array.from(arrayLike).map(function(value) {
          console.log(a);    // 可以访问到外层函数的形参a
          return fn(value, ...args);   // 可以访问到外部函数的形参fn
        });
      };
    }

Kyxy
316 声望10 粉丝