Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

call,apply and bind in JavaScript #2

Open
ZSI2017 opened this issue Mar 10, 2018 · 0 comments
Open

call,apply and bind in JavaScript #2

ZSI2017 opened this issue Mar 10, 2018 · 0 comments
Labels
JavaScript JavaScript 原生

Comments

@ZSI2017
Copy link
Owner

ZSI2017 commented Mar 10, 2018

call,apply and bind in JavaScript

在ECMAScript中,每个函数都包含两个继承而来的方法:apply() 和 call(),这两个方法的用途都是在特定的作用域中调用函数,主要作用跟bind一样,用来改变函数体内this的指向,或者说是在函数调用时改变上下文。

文章尽量使用大量实例进行讲解,它们的使用场景。同时,也会由浅入深的引导出一些理论,毕竟这几个常用方法,在MDN上都能找到合理的解释

基本功能

改变this的指向

  var fruit = {
    fruitName:"apple"
  }
  function getFruit() {
    console.log("I like "+this.fruitName)
  }

  getFruit();    // log   I like undefined
  getFruit.call(fruit)    // log   I like apple
  getFruit.apply(fruit)   // log   I like apple
  var newBind = getFruit.bind(fruit)
  newBind();              // log   I like apple

当 getFruit 并非作为一个对象的属性,而是直接当做一个函数来调用,里面的this就会被绑定到全局对象上,即window上, 所以直接调用 getFruit,里面的this指向了全局对象上,返回 undefined

在严格模式下,函数被调用后,里面的this默认是 undefined

后面,通过调用函数上的callapply方法,该变this指向,函数里面的this指向fruit

区别:
bind同样实现了改变this指向的功能,但是它不会立即执行,而是会重新创建一个绑定函数,新函数被调用时,使用bind()方法里面的第一个参数作为this

接受参数

这三个方法,从接受的第二参数开始,都直接传递给函数,但是接受参数的方法却很大的不同。

call,从第二个参数开始,以参数列表的形式展示,

apply,则把传递的函数参数,放在一个数组里面作为第二个参数。

fn.call(obj,arg1,arg2);
fn.apply(obj,[arg1,arg2])

bind,从第二个参数开始,同样以参数列表的形式,但是会提前放在新绑定函数的参数之前

 var foo = function(name,age){
   console.log("name: "+name+"- age: "+age)
 }

 var p1 = foo.bind(this,"popo");   // "popo" 作为新函数的第一个参数。
 p1(13);                       // logs    name: popo- age: 13
 p1("bobo",14)                 // logs    name: popo- age: bobo

应用场景

  • 绑定事件回调中
  $('.div-class').on('click',function(event) {
        /*TODO*/
        }.bind(this));
      }
  }

通常,我们在改变函数上下文之前,都会使用类似that = this,或者self,_this,来把this赋值给一个变量。利用.bind(),可以传入外层的上下文。

  • 循环回调

循环中利用闭包来处理回调

  for(var i = 0;i < 10;i++){
   (function(j){
       setTimeout(function(){            
           console.log(j);
       },600);
   })(i)
 }

每次循环,都会产生一个立即执行的函数,函数内部的局部变量j保存不同时期i的值,循环过程中,setTimeout回调按顺序放入消息队列中,等for循环结束后,堆栈中没有同步的代码,就去消息队列中,执行对应的回调,打印出j的值。

同理,可以利用bind,每次都创建新的函数,并且已经预先设置了参数,传入不同的指针

  function func(i) {
    console.log(i)
  }
  for(var i =0 ;i< 10;i++) {
    setTimeout(func.bind(null,i),600)
  }
  • 实现继承
 var Person = function(name,age) {
   this.name = name;
   this.age = age;
 }

 var P1 = function(name,age) {
   // 借用构造函数的方式实现继承
   // 利用call 继承了Person
   Person.call(this,name,age)
 }
 P1.prototype.getName = function() {
   console.log("name: "+this.name+", age: "+this.age);
 }

 var newPerson = new P1("popo",20);   // logs name: popo, age: 20
 newPerson.getName();

实质上,可以看成通过call()或者apply()方法,在即将新建的对象,即这里的newPerson上,执行超类型的构造函数,分别在当前上下文this上添加nameage属性。

  • 数组验证的终极方法:
  function isArray(value) {
    return Object.prototype.toString.call(value) == "[object Array]"
  }

借用了Object原生的toString()方法,打印出对应变量的构造函数名,

  • 类数组转换为数组:
  // 实现一个简单的数组 'unshift'方法
  Array.prototype.unshift = function(){
    this.splice.apply(this,
      [0,0].concat(Array.prototype.slice.apply(arguments)));
      return this.length;
  }

首先,利用this.splice.apply(),其中splice,可以直接从数组中移除或者插入变量。apply()则以数组的形式传递参数,需要利用concat拼接数组。

当函数被调用时,在函数内部会得到类数组arguments,它拥有一个length属性,但是没有任何数组的方法。所以,将slice方法中的this指向arguments,获取到arguments的长度,从而确定方法的startend下标,得到一个数组变量。

同样适用的还有,DOM里面的NodeList对象,它也是一种类数组对象。

深入理解

实现bind 方法

bind方法在ECMAScript5里面被引入,前面提到过,调用该方法时,返回一个新的函数,可以简单使用下面方法实现其改变this指向的功能。

  Function.prototype.bind = function(scope) {
    var fn = this;
    return function() {
      return fn.apply(scope)
    }
  }

接着,就可以利用concat把bind传递的预置参数拼接到新函数的参数列表中。

   Function.prototype.bind = function(scope) {
      var args = Array.prototype.slice.call(arguments,1)
      var fn = this
      return function() {
        return fn.apply(scope,args.concat(Array.prototype.slice.call(arguments)))
      }
   }

参考链接

@ZSI2017 ZSI2017 added the JavaScript JavaScript 原生 label Mar 10, 2018
@ZSI2017 ZSI2017 mentioned this issue Sep 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JavaScript JavaScript 原生
Projects
None yet
Development

No branches or pull requests

1 participant