Javascript中this绑定的3种方法与比较,js中绑定this的几种方法及简单比较

By admin in web前端 on 2019年7月2日

介绍

JavaScript(以下简称js)是一门动态语言,与传统的c,c++最大的区别就是js是在运行时动态检测值的类型和变化。这一点有很大的好处,比如可以进行隐式类型转换而不报错,书写更加灵活。当然也会造成很多问题。this是js中的一个关键字,它代表当前作用域的上下文环境,而且随着上下文的改变而动态变化。

  • this机制:this在运行时进行绑定,并不是在编写是进行绑定,它的上下文取决于函数调用时的各种条件。this的绑定和和函数的声明位置没有任何关系,只取决于函数的调用方式
  • 当一个函数被调用的时候,会创建一个活动记录(执行上下文),这个记录会包含函数在哪里被调用、函数的调用方式、传入的参数等信息,this就是这个记录的一个属性,会在函数的执行中被找到。

this 可以说是 javascript 中最耐人寻味的一个特性,学习this
的第一步就是明白 this 既不是指向函数自身也不指向函数的作用域。 this
实际上是在函数被调用时发生的绑定,它指向什么地方完全取决于函数在哪里被调用。

为什么需要绑定this

this代指当前的上下文环境,在不经意间容易改变:

var info = "This is global info";
var obj = {
    info: 'This is local info',
    getInfo: getInfo
}
function getInfo() {
    console.log(this.info);
}
obj.getInfo()    //This is local info

getInfo()    //This is global info 当前上下文环境被修改了

在上面的例子中,我们在对象内部创建一个属性getInfo,对全局作用域下的getInfo进行引用,而它的作用是打印当前上下文中info的值,当我们使用obj.getInfo进行调用时,它会打印出对象内部的info的值,此时this指向该对象。而当我们使用全局的函数时,它会打印全局环境下的info变量的值,此时this指向全局对象。

这个例子告诉我们:

  1. 同一个函数,调用的方式不同,this的指向就会不同,结果就会不同。
  2. 对象内部的属性的值为引用类型时,this的指向不会一直绑定在原对象上。

其次,还有不经意间this丢失的情况:

var info = "This is global info";
var obj = {
    info: 'This is local info',
    getInfo: function getInfo() {
        console.log(this.info);

        var getInfo2 = function getInfo2() {
            console.log(this.info);
        }
        getInfo2();
    }
}
obj.getInfo();

//This is local info
//This is global info

上面的例子中,对象obj中定义了一个getInfo方法,方法内定义了一个新的函数,也希望拿到最外层的该对象的info属性的值,但是事与愿违,函数内函数的this被错误的指向了window全局对象上面,这就导致了错误。

解决的方法也很简单,在最外层定义一个变量,存储当前词法作用域内的this指向的位置,根据变量作用域的关系,之后的函数内部还能访问这个变量,从而得到上层函数内部this的真正指向。

var info = "This is global info";
var obj = {
    info: 'This is local info',
    getInfo: function getInfo() {
        console.log(this.info);

        var self = this;          //将外层this保存到变量中
        var getInfo2 = function getInfo2() {
            console.log(self.info);    //指向外层变量代表的this
        }
        getInfo2();
    }
}
obj.getInfo();

//This is local info
//This is local info

然而这样也会有一些问题,上面的self变量等于重新引用了obj对象,这样的话可能会在有些时候不经意间修改了整个对象,而且当需要取得多个环境下的this指向时,就需要声明多个变量,不利于管理。

有一些方法,可以在不声明类似于self这种变量的条件下,绑定当前环境下的上下文,确保编程内容的安全。

四种绑定规则

为什么需要绑定this

如何绑定this

默认绑定

我们先来看一段代码:

 var name = 'global'

 function person(){
   var name = 'inner'
   console.log(this.name)
 }

 person()  // global

这里我们执行person()、输出结果是在全局上的name的值,那么为什么呢,因为这里的this指向全局对象,执行的是默认绑定。
那么我们怎么知道何时使用默认绑定呢,这里函数
person()在调用的时候没有应用任何的修饰,直接使用的是
person(),因此就只能使用默认绑定,那么使用修饰调用是什么样的、之后会进行详细说明。
但是也不是默认绑定的所以情况都是指向全局对象,当你使用严格模式编写代码时,
this指向undefined。

'use strict'
 var name = 'global'

 function person(){
   var name = 'inner'
   console.log(this.name)
 }

 person() //Cannot read property 'name' of undefined

this代指当前的上下文环境,在不经意间容易改变:

1. call, apply

call和apply是定义在Function.prototype上的两个函数,他们的作用就是修正函数执行的上下文,也就是this的指向问题。

以call为例,上述anotherFun想要输出local thing就要这样修改:

...
var anotherFun = obj.getInfo;
anotherFun.call(obj)    //This is local info

函数调用的参数:

  • Function.prototype.call(context [, argument1, argument2 ])
  • Function.prototype.apply(context [, [ arguments ] ])

从这里就可以看到,call和apply的第一参数是必须的,接受一个重新修正的上下文,第二个参数都是可选的,他们两个的区别在于,call从第二个参数开始,接受传入调用函数的值是一个一个单独出现的,而apply是接受一个数组传入。

function add(num1, num2, num3) {
  return num1 + num2 + num3;
}
add.call(null, 10, 20, 30);    //60
add.apply(null, [10, 20, 30]);    //60

当接受的context为undefined或null时,会自动修正为全局对象,上述例子中为window

隐式绑定

先上代码:

 var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 var obj = {
   name:'objName',
   person: person
 }

obj.person()  // objName

这里我们遇见第一种带有修饰的调用obj.person()
而非默认绑定中的直接调用person()

就像我们最开始说的那种,this不是在声明时进行绑定的,而是在调用时进行绑定的。当函数的引用有上下文对象时,隐式绑定会把函数调用中的this绑定到这个上下文对象,这个例子中this绑定到obj对象上,this.name就相当于obj.name,我们最终就会得到objName

下面这个例子中,说明只有在引用链的最后一层起作用。

 var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 var obj1 = {
   name:'obj1Name',
   person: person
 }

 var obj2 = {
  name:'obj2Name',
  obj1: obj1
 }

obj2.obj1.person()  // obj1Name

常见面试题:隐式丢失问题

 var name = 'global'

  function person() {
    var name = 'inner'
    console.log(this.name)
  }

  var obj1 = {
    name:'obj1Name',
    person: person
  }

 var obj2 = obj1.person

 obj2()// global

这里声明一个变量 obj2 = obj1.person 之后调用 obj(2)
虽然obj2是对obj1.person的引用,但是在下文的调用时使用的是没有任何修饰的调用,即obj2,这将应用我们上面提到的默认绑定,即非严格模式下this绑定到全局对象,
严格模式下绑定到undefined,输出golbal也符合我们的预期。

再看下面的代码:

 var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 function student(fn) {
   // fn=obj1.person
   fn()
 }

 var obj1 = {
   name: 'obj1Name',
   person: person
 }

 student(obj1.person) //global

这里执行student()是相当于 fn=obj1.person
fn是对obj1.person的引用,和上个例子中同样,发生隐式绑定丢失,对this使用默认绑定。

setTimeout:

 var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 var obj1 = {
   name: 'obj1Name',
   person: person
 }

 setTimeout(obj1.person, 1000); // node 环境下 undefined

我们来看下 mdn对this指向错误的解释:

由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window (或全局)对象,严格模式下为 undefined,这和所期望的this的值是不一样的
mdn上polyfill实现setTimeout的部分代码

 window.setTimeout = function(vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */ ) {
  var aArgs = Array.prototype.slice.call(arguments, 2);
  return __nativeST__(vCallback instanceof Function ? function() {
    vCallback.apply(null, aArgs);
  } : vCallback, nDelay);
}

我们简化一下:去掉args

 window.setTimeout = function(vCallback, nDelay ) {
  return __nativeST__(vCallback instanceof Function ? vCallback() : vCallback, nDelay);
}

我们可以看出和上面一样,vCallback = obj1.person
同样是对obj1.person的引用。

var info = "This is global info";
var obj = {
 info: 'This is local info',
 getInfo: getInfo
}
function getInfo() {
 console.log(this.info);
}
obj.getInfo() //This is local info

getInfo() //This is global info 当前上下文环境被修改了

2. 使用Function.prototype.bind进行绑定

ES5中在Function.prototype新增了bind方法,它接受一个需要绑定的上下文对象,并返回一个调用的函数的副本,同样的,它也可以在后面追加参数,实现函数的柯里化。

  • Function.prototype.bind(context[, argument1, argument2])

//函数柯里化部分
function add(num1 ,num2) {
    return num1 + num2;
}
var anotherFun = window.add.bind(window, 10);
anotherFun(20);    //30

同时,他返回一个函数的副本,并将函数永远的绑定在传入的上下文中。

...
var anotherFun = obj.getInfo.bind(obj)
anotherFun();    //This is local info

显示绑定

call、apply

一般来说基本上所有的函数都是由Function创建,Function的原型链上有两个方法call,apply

mdn上关于call和apply的方法接收的参数:
call: fun.call(thisArg, arg1, arg2, …)

apply:fun.apply(thisArg, [argsArray])

可以看出第一个参数是一样的都是this,不同点在于call接收的是参数列表,apply接收一个数组。

这种显示的更改this的指向方法,我们称之为显示绑定。

var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 var obj1 = {
   name: 'obj1Name',
   person: person
 }

 person.call(obj1) //obj1Name

这样我们虽然解决了this绑定的值,但是并没有解决之前提到的绑定丢失问题。

bind:

变种:我们封装一个函数,当每次调用函数时将this绑定到obj1上。

var name = 'global'

 function person() {
   var name = 'inner'
   console.log(this.name)
 }

 var obj1 = {
   name: 'obj1Name',
   person: person
 }

 function bind(fn,obj,...args){
   return function(){
     fn.apply(obj,args)
   }
 }

var res = bind(person,obj1)
res()  //obj1Name

同样,javascript 也为我们提供了这个方法:Function.protoype.bind()

mdn 上关于bind()的polyfill:

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}

可以看出都是和我们写的bind()核心是一样的内部调用call、apply。

在上面的例子中,我们在对象内部创建一个属性getInfo,对全局作用域下的getInfo进行引用,而它的作用是打印当前上下文中info的值,当我们使用obj.getInfo进行调用时,它会打印出对象内部的info的值,此时this指向该对象。而当我们使用全局的函数时,它会打印全局环境下的info变量的值,此时this指向全局对象。

polyfill

polyfill是一种为了向下兼容的解决方案,在不支持ES5的老旧浏览器上,如何使用bind方法呢,就得需要使用旧的方法重写一个bind方法。

if (!Function.prototype.bind) {
  Function.prototype.bind = function (obj) {
  var self = this;
   return function () {
      self.call(obj);
   }
  }
}

上面的写法实现了返回一个函数,并且将上下文修正为传入的参数,但是没有实现柯里化部分。

...
Function.prototype.bind = function(obj) {
  var args = Array.prototype.slice.call(arguments, 1);    
  //记录下所有第一次传入的参数

  var self = this;
  return function () {
    self.apply(obj, args.concat(Array.prototype.slice.call(arguments)));
  }
}

当使用bind进行绑定之后,即不能再通过call,apply进行修正this指向,所以bind绑定又称为硬绑定。

new 绑定

当new 一个函数的时候会执行一下的几步:

  1. 创建一个空对象;
  2. 将空对象的原型指向函数的property
  3. 调用apply、call 方法空对象的this指向函数
  4. 如果函数返回了一个“对象”,那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象

之前写了一篇模拟实现 new的文章
new的模拟实现

示例:

 function Person(name) {
   this.name = name
 }


var student = new Person('lili')

console.log(student.name)  // lili

这个例子告诉我们:

3. 使用new关键字进行绑定

在js中,函数有两种调用方式,一种是直接进行调用,一种是通过new关键字进行构造调用。

function fun(){console.log("function called")}
//直接调用
fun()    //function called
//构造调用
var obj = new fun()    //function called

那普通的调用和使用new关键字的构造调用之间,又有哪些区别呢?

准确的来说,就是new关键字只是在调用函数的基础上,多增加了几个步骤,其中就包括了修正this指针到return回去的对象上。

var a = 5;
function Fun() {
  this.a = 10;
}
var obj = new Fun();
obj.a    //10

ES6 箭头函数

箭头函数本身没有this,而是根据外层(函数或者全局)作用域来决定this.

 var name = 'outer'

 function person() {
   setTimeout(() => {
     console.log(this.name)
   }, 100)
 }

 var obj = {
   name: 'lili',
   person: person
 }

 obj.person() // lili

     1、同一个函数,调用的方式不同,this的指向就会不同,结果就会不同。

几种绑定方式的优先级比较

以下面这个例子来进行几种绑定状态的优先级权重的比较

var obj1 = {
  info: "this is obj1",
  getInfo: () => console.log(this.info)
}

var obj2 = {
  info: "this is obj2",
  getInfo: () => console.log(this.info)
}

    
2、对象内部的属性的值为引用类型时,this的指向不会一直绑定在原对象上。

1. call,apply和默认指向比较

首先很显然,根据使用频率来想,使用call和apply会比直接调用的优先级更高。

obj1.getInfo()    //this is obj1
obj2.getInfo()    //this is obj2
obj1.getInfo.call(obj2)    //this is obj2
obj2.getInfo.call(obj1)    //this is obj1

使用call和apply相比于使用new呢?

这个时候就会出现问题了,因为我们没办法运行类似
new function.call(something)这样的代码。所以,我们通过bind方法返回一个新的函数,再通过new判断优先级。

var obj = {}
function foo(num){
  this.num = num;
}

var setNum = foo.bind(obj);
setNum(10);
obj.num    //10

var obj2 = new setNum(20);
obj.num    //10
obj2.num    //20

通过这个例子我们可以看出来,使用new进行构造调用时,会返回一个新的对象,并将this修正到这个对象上,但是它并不会改变之前的对象内容。

那么问题来了,上面我们写的bind的polyfill明显不具备这样的能力。而在MDN上有一个bind的polyfill方法,它的方法如下:

if (!Function.prototype.bind) { 
  Function.prototype.bind = function (oThis) { 
    if (typeof this !== "function") { 
       throw new TypeError("Function.prototype.bind - 
       what is trying to be bound is not callable"); 
    }
   var aArgs = Array.prototype.slice.call(arguments, 1),
       fToBind = this, 
       fNOP = function () {}, 
       fBound = function () { 
          return fToBind.apply(this instanceof fNOP ? 
                               this : oThis || this,   
                 aArgs.concat(Array.prototype.slice.call(arguments))); 
       }; 
      fNOP.prototype = this.prototype; 
      fBound.prototype = new fNOP(); 
      return fBound; 
  };
}

上面的polyfill首先判断需要绑定的对象是否为函数,防止使用Function.prototype.bind.call(something)时,something不是一个函数造成未知错误。之后让需要返回的fBound函数继承自this,返回fBound。

其次,还有不经意间this丢失的情况:

特殊情况

当然,在某些情况下,this指针指向也存在一些意外。

var info = "This is global info";
var obj = {
 info: 'This is local info',
 getInfo: function getInfo() {
 console.log(this.info);

 var getInfo2 = function getInfo2() {
  console.log(this.info);
 }
 getInfo2();
 }
}
obj.getInfo();

//This is local info
//This is global info
箭头函数

ES6中新增了一种定义函数方式,使用”=>”进行函数的定义,在它的内部,this的指针不会改变,永远指向最外层的词法作用域。

var obj = {
  num: 1,
  getNum: function () {
    return function () {
      //this丢失
      console.log(this.num);    
      //此处的this指向window
    }
  }
}
obj.getNum()();    //undefined

var obj2 = {
  num: 2,
  getNum: function () {
    return () => console.log(this.num);    
    //箭头函数内部绑定外部getNum的this,外部this指向调用的对象
  }
}
obj2.getNum()();    //2

上面的例子中,对象obj中定义了一个getInfo方法,方法内定义了一个新的函数,也希望拿到最外层的该对象的info属性的值,但是事与愿违,函数内函数的this被错误的指向了window全局对象上面,这就导致了错误。

软绑定

上面提供的bind方法可以通过强制修正this指向,并且再不能通过call,apply进行修正。如果我们希望即能有bind效果,但是也能通过call和apply对函数进行二次修正,这个时候就需要我们重写一个建立在Function.prototype上的方法,我们给它起名为”软绑定”。

if (!Function.prototype.softBind) {
  Function.prototype.softbind = function (obj) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    return function () {
      return self.apply((!this || this === (window || global)) ? 
                        obj : this, 
                        args.concat(Array.prototype.slice.call(arguments)));
    }
  }
}

解决的方法也很简单,在最外层定义一个变量,存储当前词法作用域内的this指向的位置,根据变量作用域的关系,之后的函数内部还能访问这个变量,从而得到上层函数内部this的真正指向。

bind,call的妙用

在平日里我们需要将伪数组元素变为正常的数组元素时,往往通过Array.prototype.slice方法,正如上面的实例那样。将arguments这个对象变为真正的数组对象,使用
Array.prototype.slice.call(arguments)进行转化.。但是,每次使用这个方法太长而且繁琐。所以有时候我们就会这样写:

var slice = Array.prototype.slice;
slice(arguments);
//error

同样的问题还出现在:

var qsa = document.querySelectorAll;
qsa(something);
//error

上面的问题就出现在,内置的slice和querySelectorAll方法,内部使用了this,当我们简单引用时,this在运行时成为了全局环境window,当然会造成错误。我们只需要简单的使用bind,就能创建一个函数副本。

var qsa = document.querySelectorAll.bind(document);
qsa(something);

同样的,使用因为call和apply也是一个函数,所以也可以在它们上面调用bind方法。从而使返回的函数的副本本身就带有修正指针的功能。

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice(arguments);
var info = "This is global info";
var obj = {
 info: 'This is local info',
 getInfo: function getInfo() {
 console.log(this.info);

 var self = this;  //将外层this保存到变量中
 var getInfo2 = function getInfo2() {
  console.log(self.info); //指向外层变量代表的this
 }
 getInfo2();
 }
}
obj.getInfo();

//This is local info
//This is local info

然而这样也会有一些问题,上面的self变量等于重新引用了obj对象,这样的话可能会在有些时候不经意间修改了整个对象,而且当需要取得多个环境下的this指向时,就需要声明多个变量,不利于管理。

有一些方法,可以在不声明类似于self这种变量的条件下,绑定当前环境下的上下文,确保编程内容的安全。

如何绑定this

1. call, apply

call和apply是定义在Function.prototype上的两个函数,他们的作用就是修正函数执行的上下文,也就是this的指向问题。

以call为例,上述anotherFun想要输出local thing就要这样修改:

...
var anotherFun = obj.getInfo;
anotherFun.call(obj) //This is local info

函数调用的参数:

      Function.prototype.call(context [, argument1, argument2 ])

      Function.prototype.apply(context [, [ arguments ] ])

从这里就可以看到,call和apply的第一参数是必须的,接受一个重新修正的上下文,第二个参数都是可选的,他们两个的区别在于,call从第二个参数开始,接受传入调用函数的值是一个一个单独出现的,而apply是接受一个数组传入。

function add(num1, num2, num3) {
 return num1 + num2 + num3;
}
add.call(null, 10, 20, 30); //60
add.apply(null, [10, 20, 30]); //60

当接受的context为undefined或null时,会自动修正为全局对象,上述例子中为window

2.
使用Function.prototype.bind进行绑定

ES5中在Function.prototype新增了bind方法,它接受一个需要绑定的上下文对象,并返回一个调用的函数的副本,同样的,它也可以在后面追加参数,实现函数的柯里化。

Function.prototype.bind(context[, argument1, argument2])
//函数柯里化部分
function add(num1 ,num2) {
 return num1 + num2;
}
var anotherFun = window.add.bind(window, 10);
anotherFun(20); //30

同时,他返回一个函数的副本,并将函数永远的绑定在传入的上下文中。

...
var anotherFun = obj.getInfo.bind(obj)
anotherFun(); //This is local info

polyfill

polyfill是一种为了向下兼容的解决方案,在不支持ES5的老旧浏览器上,如何使用bind方法呢,就得需要使用旧的方法重写一个bind方法。

if (!Function.prototype.bind) {
 Function.prototype.bind = function (obj) {
 var self = this;
 return function () {
 self.call(obj);
 }
 }
}

上面的写法实现了返回一个函数,并且将上下文修正为传入的参数,但是没有实现柯里化部分。

...
Function.prototype.bind = function(obj) {
 var args = Array.prototype.slice.call(arguments, 1); 
 //记录下所有第一次传入的参数

 var self = this;
 return function () {
 self.apply(obj, args.concat(Array.prototype.slice.call(arguments)));
 }
}

当使用bind进行绑定之后,即不能再通过call,apply进行修正this指向,所以bind绑定又称为硬绑定。

3. 使用new关键字进行绑定

在js中,函数有两种调用方式,一种是直接进行调用,一种是通过new关键字进行构造调用。

function fun(){console.log("function called")}
//直接调用
fun() //function called
//构造调用
var obj = new fun() //function called

那普通的调用和使用new关键字的构造调用之间,又有哪些区别呢?

准确的来说,就是new关键字只是在调用函数的基础上,多增加了几个步骤,其中就包括了修正this指针到return回去的对象上。

var a = 5;
function Fun() {
 this.a = 10;
}
var obj = new Fun();
obj.a //10

几种绑定方式的优先级比较

以下面这个例子来进行几种绑定状态的优先级权重的比较

var obj1 = {
 info: "this is obj1",
 getInfo: () => console.log(this.info)
}

var obj2 = {
 info: "this is obj2",
 getInfo: () => console.log(this.info)
}

1. call,apply和默认指向比较

首先很显然,根据使用频率来想,使用call和apply会比直接调用的优先级更高。

obj1.getInfo() //this is obj1
obj2.getInfo() //this is obj2
obj1.getInfo.call(obj2) //this is obj2
obj2.getInfo.call(obj1) //this is obj1

使用call和apply相比于使用new呢?

这个时候就会出现问题了,因为我们没办法运行类似
new function.call(something)这样的代码。所以,我们通过bind方法返回一个新的函数,再通过new判断优先级。

var obj = {}
function foo(num){
 this.num = num;
}

var setNum = foo.bind(obj);
setNum(10);
obj.num //10

var obj2 = new setNum(20);
obj.num //10
obj2.num //20

通过这个例子我们可以看出来,使用new进行构造调用时,会返回一个新的对象,并将this修正到这个对象上,但是它并不会改变之前的对象内容。

那么问题来了,上面我们写的bind的polyfill明显不具备这样的能力。而在MDN上有一个bind的polyfill方法,它的方法如下:

if (!Function.prototype.bind) { 
 Function.prototype.bind = function (oThis) { 
 if (typeof this !== "function") { 
 throw new TypeError("Function.prototype.bind - 
 what is trying to be bound is not callable"); 
 }
 var aArgs = Array.prototype.slice.call(arguments, 1),
 fToBind = this, 
 fNOP = function () {}, 
 fBound = function () { 
  return fToBind.apply(this instanceof fNOP ? 
    this : oThis || this, 
   aArgs.concat(Array.prototype.slice.call(arguments))); 
 }; 
 fNOP.prototype = this.prototype; 
 fBound.prototype = new fNOP(); 
 return fBound; 
 };
}

上面的polyfill首先判断需要绑定的对象是否为函数,防止使用Function.prototype.bind.call(something)时,something不是一个函数造成未知错误。之后让需要返回的fBound函数继承自this,返回fBound。

特殊情况

当然,在某些情况下,this指针指向也存在一些意外。

箭头函数

ES6中新增了一种定义函数方式,使用”=>”进行函数的定义,在它的内部,this的指针不会改变,永远指向最外层的词法作用域。

var obj = {
 num: 1,
 getNum: function () {
 return function () {
 //this丢失
 console.log(this.num); 
 //此处的this指向window
 }
 }
}
obj.getNum()(); //undefined

var obj2 = {
 num: 2,
 getNum: function () {
 return () => console.log(this.num); 
 //箭头函数内部绑定外部getNum的this,外部this指向调用的对象
 }
}
obj2.getNum()(); //2

软绑定

上面提供的bind方法可以通过强制修正this指向,并且再不能通过call,apply进行修正。如果我们希望即能有bind效果,但是也能通过call和apply对函数进行二次修正,这个时候就需要我们重写一个建立在Function.prototype上的方法,我们给它起名为”软绑定”。

if (!Function.prototype.softBind) {
 Function.prototype.softbind = function (obj) {
 var self = this;
 var args = Array.prototype.slice.call(arguments, 1);
 return function () {
 return self.apply((!this || this === (window || global)) ? 
   obj : this, 
   args.concat(Array.prototype.slice.call(arguments)));
 }
 }
}

bind,call的妙用

在平日里我们需要将伪数组元素变为正常的数组元素时,往往通过Array.prototype.slice方法,正如上面的实例那样。将arguments这个对象变为真正的数组对象,使用
Array.prototype.slice.call(arguments)进行转化.。但是,每次使用这个方法太长而且繁琐。所以有时候我们就会这样写:

var slice = Array.prototype.slice;
slice(arguments);
//error

同样的问题还出现在:

var qsa = document.querySelectorAll;
qsa(something);
//error

上面的问题就出现在,内置的slice和querySelectorAll方法,内部使用了this,当我们简单引用时,this在运行时成为了全局环境window,当然会造成错误。我们只需要简单的使用bind,就能创建一个函数副本。

var qsa = document.querySelectorAll.bind(document);
qsa(something);

同样的,使用因为call和apply也是一个函数,所以也可以在它们上面调用bind方法。从而使返回的函数的副本本身就带有修正指针的功能。

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice(arguments);

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或工作能带来一定的帮助,如果有疑问大家可以留言交流。

您可能感兴趣的文章:

  • JavaScript中this的用法实例分析
  • JS作用域闭包、预解释和this关键字综合实例解析
  • JS中this上下文对象使用方式
  • JavaScript中this的四个绑定规则总结
  • 几句话带你理解JS中的this、闭包、原型链
  • javascript this详细介绍
  • 学习掌握JavaScript中this的使用技巧
  • tomcat6下jsp出现getOutputStream() has already been called for this
    response异常的原因和解决方法
  • JS中改变this指向的方法(call和apply、bind)
  • Javascript的this用法

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2020 澳门新葡亰官网app 版权所有