关闭

[置顶] 别说你懂this

789人阅读 评论(1) 收藏 举报
分类:

 参考文章:http://www.cnblogs.com/beyond-succeed/p/5808234.html

     首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁实际上this的最终指向的是那个调用它的对象这句话有些问题,后面会解释为什么会有问题,虽然网上大部分的文章都是这样说的,虽然在很多情况下那样去理解不会出什么问题,但是实际上那样理解是不准确的,所以在你理解this的时候会有种琢磨不透的感觉,那么接下来我会深入的探讨这个问题。

  为什么要学习this如果你学过函数式编程,面向对象编程,那你肯定知道干什么用的,如果你没有学过,那么暂时可以不用看这篇文章,当然如果你有兴趣也可以看看,毕竟这是js中必须要掌握的东西。

例子1:

function a(){
    var user = "小J";
    console.log(this.user); //undefined
    console.log(this); //Window
}
a();

按照我们上面说的this最终指向的是调用它的对象,这里的函数a实际是被Window对象所点出来的,下面的代码就可以证明。

function a(){
    var user = "小J";
  console.log(
this.user); //undefined console.log(this);  
}
window.a()
//Window

和上面代码一样吧,其实alert也是window的一个属性,也是window点出来的

 

例子2:

复制代码
var o = {
    user:"追梦子",
    fn:function(){
        console.log(this.user);  //追梦子
    }
}
o.fn();
复制代码

  这里的this指向的是对象o,因为你调用这个fn是通过o.fn()执行的,那自然指向就是对象o,这里再次强调一点,this的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,一定要搞清楚这个

其实例子1和例子2说的并不够准确,下面这个例子就可以推翻上面的理论。

如果要彻底的搞懂this必须看接下来的几个例子.

 

例子3:

var o = { user:"追梦子", fn:function(){ console.log(this.user); //追梦子 } } window.o.fn();

  这段代码和上面的那段代码几乎是一样的,但是这里的this为什么不是指向window,如果按照上面的理论,最终this指向的是调用它的对象,这里先说个而外话,window是js中的全局对象,我们创建的变量实际上是给window添加属性,所以这里可以用window点o对象。

这里先不解释为什么上面的那段代码this为什么没有指向window,我们再来看一段代码。

复制代码
var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //12
        }
    }
}
o.b.fn();
复制代码

这里同样也是对象o点出来的,但是同样this并没有执行它,那你肯定会说我一开始说的那些不就都是错误的吗?其实也不是,只是一开始说的不准确,接下来我将补充一句话,我相信你就可以彻底的理解this的指向的问题。

================================================>>>>>

情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。
================================================>>>>>

情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。

================================================>>>>>

情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象例子3可以证明,如果不相信,那么接下来我们继续看几个例子。

复制代码
var o = {
    a:10,
    b:{
        // a:12,
        fn:function(){
            console.log(this.a); //undefined
        }
    }
}
o.b.fn();
复制代码

尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。

 

还有一种比较特殊的情况,例子4:

复制代码
var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();
复制代码

这里this指向的是window,是不是有些蒙了?其实是因为你没有理解一句话,这句话同样至关重要。

  this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子4中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window,这和例子3是不一样的,例子3是直接执行了fn。

  this讲来讲去其实就是那么一回事,只不过在不同的情况下指向的会有些不同,上面的总结每个地方都有些小错误,也不能说是错误,而是在不同环境下情况就会有不同,所以我也没有办法一次解释清楚,只能你慢慢地的去体会。

 

构造函数版this:

function Fn(){
    this.user = "小J";
}
var a = new Fn();
console.log(a.user); //小J

  这里之所以对象a可以点出函数Fn里面的user是因为new关键字可以改变this的指向,将这个this指向对象a,为什么我说a是对象,因为用了new关键字就是创建一个对象实例,理解这句话可以想想我们的例子3,我们这里用变量a创建了一个Fn的实例(相当于复制了一份Fn到对象a里面),此时仅仅只是创建,并没有执行,而调用这个函数Fn的是对象a,那么this指向的自然是对象a,那么为什么对象Fn中会有user,因为你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份。

除了上面的这些以外,我们还可以自行改变this的指向 ========>>> call, apply, bind

更新一个小问题当this碰到return

复制代码
function fn()  
{  
    this.user = '小J';  
    return {};  
}
var a = new fn;  
console.log(a.user); //undefined
复制代码

再看一个

复制代码
function fn()  
{  
    this.user = '小J';  
    return function(){};
}
var a = new fn;  
console.log(a.user); //undefined
复制代码

再来

复制代码
function fn()  
{  
    this.user = '小J';  
    return 1;
}
var a = new fn;  
console.log(a.user); //小J
复制代码
复制代码
function fn()  
{  
    this.user = '小J';  
    return undefined;
}
var a = new fn;  
console.log(a.user); //小J
复制代码

如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

复制代码
function fn()  
{  
    this.user = '小J';  
    return undefined;
}
var a = new fn;  
console.log(a); //fn {user: "小J"}
复制代码

还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。

复制代码
function fn()  
{  
    this.user = '小J';  
    return null;
}
var a = new fn;  
console.log(a.user); //小J
复制代码

知识点补充

1.在严格版中的默认的this不再是window,而是undefined。

2.new操作符会改变函数this的指向问题,虽然我们上面讲解过了,但是并没有深入的讨论这个问题,网上也很少说,所以在这里有必要说一下。

function fn(){
    this.num = 1;
}
var a = new fn();
console.log(a.num); //1

  为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。




















在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象就是类的实例。但是在Javascript语言体系中,是不存在类(Class)的概念的,javascript中不是基于‘类的’,而是通过构造函数(constructor)和原型链(prototype chains)实现的。但是在ES6中提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让原型对象的写法更加清晰、更像面向对象编程的语法而已。

 

 

  按照我的习惯,在写文章前我会给出文章目录。

  以下内容会分为如下小节:

  1.构造函数的简单介绍

  2.构造函数的缺点

  3.prototype属性的作用

  4.原型链(prototype chain)

  5.constructor属性

    5.1:constructor属性的作用

  6.instanceof运算符

 

1.构造函数的简单介绍

  在我的一篇Javascript 中构造函数与new命令的密切关系文章中,详细了介绍了构造函数的概念和特点,new命令的原理和用法等,如果对于构造函数不熟悉的同学,可以前往细细品味。以下做一个简单的回顾。

  所谓构造函数,就是提供了一个生成对象的模板并描述对象的基本结构的函数。一个构造函数,可以生成多个对象,每个对象都有相同的结构。总的来说,构造函数就是对象的模板,对象就是构造函数的实例。

  构造函数的特点有:

    a:构造函数的函数名首字母必须大写。

    b:内部使用this对象,来指向将要生成的对象实例。

    c:使用new操作符来调用构造函数,并返回对象实例。

  看一个最简单的一个例子。

复制代码
1     function Person(){
2         this.name = 'keith';
3     }
4 
5     var boy = new Person();
6     console.log(boy.name);    //'keith'
复制代码

 

 

2.构造函数的缺点

  所有的实例对象都可以继承构造函数中的属性和方法。但是,同一个对象实例之间,无法共享属性。

复制代码
 1     function Person(name,height){
 2         this.name=name;
 3         this.height=height;
 4         this.hobby=function(){
 5             return 'watching movies';
 6         }
 7     }
 8 
 9     var boy=new Person('keith',180);
10     var girl=new Person('rascal',153);
11 
12     console.log(boy.name);    //'keith'
13     console.log(girl.name);    //'rascal'
14     console.log(boy.hobby===girl.hobby);  //false
复制代码

  上面代码中,一个构造函数Person生成了两个对象实例boy和girl,并且有两个属性和一个方法。但是,它们的hobby方法是不一样的。也就是说,每当你使用new来调用构造函数放回一个对象实例的时候,都会创建一个hobby方法。这既没有必要,又浪费资源,因为所有hobby方法都是童颜的行为,完全可以被两个对象实例共享。

  所以,构造函数的缺点就是:同一个构造函数的对象实例之间无法共享属性或方法。

 

3.prototype属性的作用

  为了解决构造函数的对象实例之间无法共享属性的缺点,js提供了prototype属性。

  js中每个数据类型都是对象(除了null和undefined),而每个对象都继承自另外一个对象,后者称为“原型”(prototype)对象,只有null除外,它没有自己的原型对象。

  原型对象上的所有属性和方法,都会被对象实例所共享

  通过构造函数生成对象实例时,会将对象实例的原型指向构造函数的prototype属性。每一个构造函数都有一个prototype属性,这个属性就是对象实例的原型对象。

复制代码
 1     function Person(name,height){
 2         this.name=name;
 3         this.height=height;
 4     }
 5 
 6     Person.prototype.hobby=function(){
 7         return 'watching movies';
 8     }
 9 
10     var boy=new Person('keith',180);
11     var girl=new Person('rascal',153);
12 
13     console.log(boy.name);    //'keith'
14     console.log(girl.name);    //'rascal'
15     console.log(boy.hobby===girl.hobby);  //true
复制代码

  上面代码中,如果将hobby方法放在原型对象上,那么两个实例对象都共享着同一个方法。我希望大家都能理解的是,对于构造函数来说,prototype是作为构造函数的属性;对于对象实例来说,prototype是对象实例的原型对象。所以prototype即是属性,又是对象。

  原型对象的属性不是对象实例的属性。对象实例的属性是继承自构造函数定义的属性,因为构造函数内部有一个this关键字来指向将要生成的对象实例。对象实例的属性,其实就是构造函数内部定义的属性。只要修改原型对象上的属性和方法,变动就会立刻体现在所有对象实例上。

复制代码
1     Person.prototype.hobby=function(){
2         return 'swimming';
3     }
4     console.log(boy.hobby===girl.hobby);  //true
5     console.log(boy.hobby());    //'swimming'
6     console.log(girl.hobby());    //'swimming'
复制代码

  上面代码中,当修改了原型对象的hobby方法之后,两个对象实例都发生了变化。这是因为对象实例其实是没有hobby方法,都是读取原型对象的hobby方法。也就是说,当某个对象实例没有该属性和方法时,就会到原型对象上去查找。如果实例对象自身有某个属性或方法,就不会去原型对象上查找。

1     boy.hobby=function(){
2         return 'play basketball';
3     }
4     console.log(boy.hobby());    //'play basketball'
5     console.log(girl.hobby());    //'swimming'

  上面代码中,boy对象实例的hobby方法修改时,就不会在继承原型对象上的hobby方法了。不过girl仍然会继承原型对象的方法。

  总结一下:

  a:原型对象的作用,就是定义所有对象实例所共享的属性和方法。

  b:prototype,对于构造函数来说,它是一个属性;对于对象实例来说,它是一个原型对象。

  

4.原型链(prototype chains)

  对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型对象本身对于对象实例来说也是对象,它也有自己的原型,所以形成了一条原型链(prototype chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。所有一切的对象的原型顶端,都是Object.prototype,即Object构造函数的prototype属性指向的那个对象。

  当然,Object.prototype对象也有自己的原型对象,那就是没有任何属性和方法的null对象,而null对象没有自己的原型。

1     console.log(Object.getPrototypeOf(Object.prototype));    //null
2     console.log(Person.prototype.isPrototypeOf(boy))    //true

  原型链(prototype chain)的特点有:

    a:读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined

    b:如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overiding)。

    c:一级级向上在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。

  看概念可能比较晦涩,我们来看一个例子。但是理解了概念真的很重要。

1     var arr=[1,2,3];
2     console.log(arr.length);    //3
3     console.log(arr.valueOf())    //[1,2,3]
4     console.log(arr.join('|'))    //1|2|3    

  上面代码中,定了一个数组arr,数组里面有三个元素。我们并没有给数组添加任何属性和方法,可是却在调用length,join(),valueOf()时,却不会报错。

  length属性是继承自Array.prototype的,属于原型对象上的一个属性。join方法也是继承自Array.prototype的,属于原型对象上的一个方法。这两个方法是所有数组所共享的。当实例对象上没有这个length属性时,就会去原型对象查找。

  valueOf方法是继承自Object.prototype的。首先,arr数组是没有valueOf方法的,所以就到原型对象Array.prototype查找。然后,发现Array.prototype对象上没有valueOf方法。最后,再到它的原型对象Object.prototype查找。

  来看看Array.prototype对象和Object.prototype对象分别有什么属性和方法。

复制代码
1     console.log(Object.getOwnPropertyNames(Array.prototype))
2 //["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push", "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf", "forEach", "map", "filter", "reduce", "reduceRight", "some", "every", "find", "findIndex", "copyWithin", "fill", "entries", "keys", "values", "includes", "constructor", "$set", "$remove"]
3     console.log(Object.getOwnPropertyNames(Object.prototype))
4 // ["toSource", "toString", "toLocaleString", "valueOf", "watch", "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__", "constructor"]
复制代码

   我相信,大家看到这里,对prototype还是似懂非懂的。这很正常,毕竟是js中比较重要又比较抽象的概念,不可能那么快就掌握,再啃多几篇,说不定掌握其精髓。在某乎上,有一个活生生的实例,可能也是大家会遇到的问题。可以看看 js构造函数和原型对象

 

5.constructor属性

  prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。

1     function A(){};
2     console.log(A.prototype.constructor===A)    //true

  要注意的是,prototype是构造函数的属性,而constructor则是构造函数的prototype属性所指向的那个对象,也就是原型对象的属性。注意不要混淆。

1     console.log(A.hasOwnProperty('prototype'));    //true
2     console.log(A.prototype.hasOwnProperty('constructor')); //true

  由于constructor属性是定义在原型(prototype)对象上面,意味着可以被所有实例对象继承。

1     function A(){};
2     var a=new A();
3 
4     console.log(a.constructor);    //A()
5     console.log(a.constructor===A.prototype.constructor);//true

  上面代码中,a是构造函数A的实例对象,但是a自身没有contructor属性,该属性其实是读取原型链上面的A.prototype.constructor属性。

  5.1:constructor属性的作用

    a:分辨原型对象到底属于哪个构造函数

1     function A(){};
2     var a=new A();
3 
4     console.log(a.constructor===A)    //true
5     console.log(a.constructor===Array)    //false

    上面代码表示,使用constructor属性,确定实例对象a的构造函数是A,而不是Array

    b:从实例新建另一个实例

1     function A() {};
2     var a = new A();
3     var b = new a.constructor();
4     console.log(b instanceof A);    //true

    上面代码中,a是构造函数A的实例对象,可以从a.constructor间接调用构造函数。

    c:调用自身的构造函数成为可能

1     A.prototype.hello = function() {
2         return new this.constructor();
3     }

    d:提供了一种从构造函数继承另外一种构造函数的模式

复制代码
1     function Father() {}
2 
3     function Son() {
4         Son.height.constructor.call(this);
5     }
6 
7     Son.height = new Father();
复制代码

    上面代码中,FatherSon都是构造函数,在Son内部的this上调用Father,就会形成Son继承Father的效果。

    e:由于constructor属性是一种原型对象和构造函数的关系,所以在修改原型对象的时候,一定要注意constructor的指向问题。

    解决方法有两种,要么将constructor属性指向原来的构造函数,要么只在原型对象上添加属性和方法,避免instanceof失真。

 

6.instanceof运算符

  instanceof运算符返回一个布尔值,表示指定对象是否为某个构造函数的实例。

1     function A() {};
2     var a = new A();
3     console.log(a instanceof A);    //true

  因为instanceof对整个原型链上的对象都有效,所以同一个实例对象,可能会对多个构造函数都返回true。

1     function A() {};
2     var a = new A();
3     console.log(a instanceof A);    //true
4     console.log(a instanceof Object);    //true

  注意,instanceof对象只能用于复杂数据类型(数组,对象等),不能用于简单数据类型(布尔值,数字,字符串等)。

复制代码
1     var x = [1];
2     var o = {};
3     var b = true;
4     var c = 'string';
5     console.log(x instanceof Array);    //true
6     console.log(o instanceof Object);    //true
7     console.log(b instanceof Boolean);    //false
8     console.log(c instanceof String);    //false
复制代码

  此外,null和undefined都不是对象,所以instanceof 总是返回false。

1     console.log(null instanceof Object);    //false
2     console.log(undefined instanceof Object);    //false

  利用instanceof运算符,还可以巧妙地解决,调用构造函数时,忘了加new命令的问题。

复制代码
1     function Keith(name,height) {
2         if (! this instanceof Keith) {
3             return new Keith(name,height);
4         }
5         this.name = name;
6         this.height = height;
7     }
复制代码

  上面代码中,使用了instanceof运算符来判断函数体内的this关键字是否指向构造函数Keith的实例,如果不是,就表明忘记加new命令,此时构造函数会返回一个对象实例,避免出现意想不到的结果。

 

 

    因为限于篇幅的原因,暂时介绍到这里。

  我会在下次的分享中谈谈原型(prototype)对象的一些原生方法,如Object.getPrototypeOf(),Object.setPrototypeOf()等,并且介绍获取原生对象方法的比较。

 

 

 

完。

 

 

感谢大家的阅读。





















最近在读一本进阶的JavaScript的书《你不知道的JavaScript(上卷)》,这次研究了一下“this”。

当一个函数被调用时,会创建一个活动记录(执行上下文)。

这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。

this就是记录的其中一个属性,会在函数执行的过程中用到。

this既不指向函数自身也不指向函数的作用域

this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用

 

一、调用位置

调用位置就在当前正在执行的函数的前一个调用中,源码查看

复制代码
function baz() {
  // 当前调用栈是:baz
  // 因此,当前调用位置是全局作用域
  console.log("baz");
  bar(); // <-- bar 的调用位置
}

function bar() {
  // 当前调用栈是 baz -> bar
  // 因此,当前调用位置在 baz 中
  console.log("bar");
  foo(); // <-- foo 的调用位置
}

function foo() {
  // 当前调用栈是 baz -> bar -> foo
  // 因此,当前调用位置在 bar 中
  console.log("foo");
}
baz(); // <-- baz 的调用位置
复制代码

 

二、绑定规则

你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。

1)默认绑定

最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则

function foo() {
  console.log(this.a);
}
var a = 2;
foo(); // 2

 

2)隐式绑定

隐式绑定的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含

复制代码
function foo() {
  console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 2
复制代码

但有时候会出现隐式丢失

复制代码
function foo() {
  console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
var bar = obj.foo; // 函数  
var a = "oops, global"; //
bar(); // "oops, global"
复制代码

虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身

因此此时的bar()其实是一个不带任何修饰的函数调用,应用了默认绑定

 

3)显式绑定

使用函数的call(..)和apply(..)方法。

复制代码
function foo() {
  console.log(this.a);
}
var obj = {
    a: 2
};
foo.call(obj); // 2
复制代码

在很多库中经常能看到bind方法,这是一种硬绑定,一种显式的强制绑定,下面是一种bind实现。

复制代码
function foo(something) {
  console.log(this.a, something);
  return this.a + something;
}
// 简单的辅助绑定函数 
function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  };
}
var obj = {
  a: 2
};
var bar = bind(foo, obj);
var b = bar(3); // 2 3
console.log(b); // 5
复制代码

 

4)new绑定

使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:

1. 创建(或者说构造)一个全新的对象。

2. 这个新对象会被执行[[原型]]连接。

3. 这个新对象会绑定到函数调用的this

4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

function foo(a) {
  this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2

 

三、优先级

默认绑定的优先级是四条规则中最低的。

1)显式绑定优先级比隐式绑定要更高

复制代码
function foo() {
  console.log(this.a);
}
var obj1 = {
    a: 2,
    foo: foo
};
var obj2 = {
    a: 3,
    foo: foo
};

obj1.foo(); // 2 
obj2.foo(); // 3

obj1.foo.call(obj2); // 3 
obj2.foo.call(obj1); // 2
复制代码

 

2)new绑定比隐式绑定优先级高

复制代码
function foo(something) {
  this.a = something;
}
var obj1 = {
    foo: foo
};

obj1.foo(2);
console.log(obj1.a); // 2

var bar = new obj1.foo(4);
console.log(obj1.a); // 2 
console.log(bar.a); // 4
复制代码

 

3)new绑定会修改显示绑定中this

复制代码
function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  };
}
function foo(something) { 
  this.a = something;
}
 
var obj1 = {};
var bar = foo.bind(obj1); //bar被硬绑定到obj1上
bar(2);
console.log( obj1.a ); // 2
 
var baz = new bar(3); //
console.log( obj1.a ); // 没有把obj1.a 修改为3,还是为2 
console.log( baz.a ); // 3
复制代码

 

4)判断this

1. 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。

var bar = new foo()

2. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。

var bar = foo.call(obj2)

3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。

var bar = obj1.foo()

4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

var bar = foo()

 

四、绑定例外

1)被忽略的this

如果你把null或者undefined作为this的绑定对象传入call、apply或者bind。

这些值在调用时会被忽略,实际应用的是默认绑定规则。

function foo() {
  console.log(this.a);
}
var a = 2;
foo.call(null); // 2

 

2)间接引用

你有可能(有意或者无意地)创建一个函数的“间接引用”。

在这种情况下,调用这个函数会应用默认绑定规则。

复制代码
function foo() {
  console.log(this.a);
}
var a = 2; 
var o = { a: 3, foo: foo }; 
var p = { a: 4 }; 

o.foo(); // 3
(p.foo = o.foo)(); // 2
复制代码

赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()。

 

3)软绑定

如果可以给默认绑定指定一个全局对象和undefined以外的值。

那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改this的能力。

可以通过一种被称为软绑定的方法来实现我们想要的效果。

复制代码
Function.prototype.softBind = function(obj) {
  var fn = this;
  // 捕获所有 curried 参数
  var curried = [].slice.call(arguments, 1);
  var bound = function() {
    return fn.apply((!this || this === (window || global)) ? obj : this, curried.concat.apply(curried, arguments));
  };
  bound.prototype = Object.create(fn.prototype);
  return bound;
};
复制代码
复制代码
function foo() {
  console.log("name: " + this.name);
}
var obj = { name: "obj" }, 
    obj2 = { name: "obj2" }, 
    obj3 = { name: "obj3" };
 
var fooOBJ = foo.softBind( obj ); 

fooOBJ(); // name: obj <---- 应用了软绑定

obj2.foo = foo.softBind(obj); 
obj2.foo(); // name: obj2 

fooOBJ.call( obj3 ); // name: obj3

setTimeout( obj2.foo, 10 );// name: obj   <---- 应用了软绑定
复制代码

软绑定版本的foo()可以手动将this绑定到obj2或者obj3上。

但如果应用默认绑定,则会将this绑定到obj。



3
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:3133658次
    • 积分:63376
    • 等级:
    • 排名:第31名
    • 原创:3032篇
    • 转载:41篇
    • 译文:1篇
    • 评论:262条
    如有任何疑问,欢迎加微信切磋!
    文章分类