你不知道的JS读书笔记2—this对象

本文是在《你不知道的JS》上阅读了其他的资料之后整理的。

this是很重要的一个对象,它不指向它本身不指向它的作用域,它指向函数调用所处的位置。需要分析函数的调用栈,调用位置就是调用栈中当前函数的上一个函数。

比如如下代码:

function foo(){
    console.log(this.a);
}

var a = 2;

var obj = {
    a : 1,
    foo: foo
}

obj.foo();   // 1 调用栈为 obj->foo,所以this指向obj
var bar = obj.foo;
foo();  // 2 foo在全局作用域下被调用,所以this指向全局作用域
bar();  // 2  bar其实就是foo,所以调用栈就是foo,this指向全局作用域

首先函数的调用方式大致分为普通函数调用,作为对象方法调用,call/apply调用,new构造器调用。我们分别来说这几种情况下this的指向问题,以及几种调用情况共存时候的优先级。

new绑定

new一个对象的具体过程如下:

  1. 新创建一个对象;
  2. 建立这个对象与原型链的联系;
  3. 将这个新对象绑定this;
  4. 如果没有返回的对象,就自动返回this。

所以用new创建出来的函数,this都指向这个新建的实例。

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

 var bar = new foo();
 bar.hello();  // 2

正常情况下,foo函数中的this指向全局作用域,但是new的操作强行将新创建的bar对象绑定this。new绑定的优先级是最高的。

显示绑定

直接用apply或者call的方式强行指定this对象。

function foo(){
    console.log(this.a);
}

var a = 2;

var obj = {
    a : 1
};

var obj2 = {
    a : 10
};

foo.call( obj );  // 1 强制将this指向obj对象
foo.call( obj2 );  // 10 强制将this指向obj2对象
foo();   //2

这里仍然有个问题,foo的this可以被改变的。下面这种硬绑定的方法可以在一般情况下,foo的this固定。

function foo(){
    console.log(this.a);
}

var obj = {
    a : 1
};

var obj1 = {
    a : 10
};

function bar(){
   return  foo.apply(obj, arguments) ;
}

bar();  // 1

bar.apply(obj1);  // 1  this仍然指向obj,未改变

ES5中封装了bind函数,去实现上述的功能。简单的实现版本如下

Function.prototype.bind = function(context){
     var self = this;

     return function(){
         return self.apply(context, arguments);
     }
 }

另外,在原生JS中,有些接口本来就能做this的设置,比如forEach, map, every, some, map, filter等。显示绑定的优先级仅次于new绑定。

隐式绑定

当函数作为对象的方法被调用时,this指向这个调用的对象。没什么特别之处,按照调用栈来看就好。优先级次之。需要注意的是,当多个属性嵌套时,使用最近原则。

function foo(){
    console.log(this.a);
}

var a = 2;

var obj = {
    a : 1,
    foo: foo,
    obj2: obj2
}

var obj2 = {
    a : 2,
    foo: foo
}

obj.obj2.foo();   // 2 调用栈为 obj->obj2->foo,所以this指向obj2

默认绑定

就是普通函数的调用。需要说明的是,在非严格模式下,this指向全局对象window,而严格模式下,this指向undefined。优先级最低。如果绑定null或者undefined,应用的是默认绑定规则。

箭头函数

我们先来看个例子。

var point = {
    x : 0,
    y : 0,
    moveTo : function(x, y) {
        // 内部函数
       var moveX = function(x) {
            this.x = x;//this 绑定到了哪里?
       };
       // 内部函数
       var moveY = function(y) {
           this.y = y;//this 绑定到了哪里?
       };

       moveX(x);
       moveY(y);
    }
};
point.moveTo(1, 1);
point.x; //==>0
point.y; //==>0
x; //==>1
y; //==>1

这个例子看起来不是上面四个规则中的任何一个。 point.moveTo是以对象属性方法的方式调用的,当moveTo函数内部this应该指向point对象。当它并没有直接在moveTo函数内部直接调用this,而是在内部又定义了两个函数变量moveX,moveY,将两个匿名函数赋值给它们。那匿名函数内部的this应该指向point.moveTo? ………..(此时省略N个字,具体的原因谁能说说) 总之,this的绑定对象莫名其妙地绑定了全局变量。

为了避免这种情况的发生,一般经典的做法为:

var point = {
    x : 0,
    y : 0,
    moveTo : function(x, y) {
       var that = this;
       // 内部函数
       var moveX = function(x) {
        that.x = x;
       };
       // 内部函数
       var moveY = function(y) {
        that.y = y;
       }
       moveX(x);
       moveY(y);
    }
};
point.moveTo(1, 1);
point.x; //==>1
point.y; //==>1

另外,ES6出来了一个箭头函数,不仅是形式上的简化,它还严格符合词法作用域。可以替换平时写代码时加的var that = this

var point = {
    x : 0,
    y : 0,
    moveTo : function(x, y) { 
         // 内部函数
         var moveX =  x => {this.x = x;};
         // 内部函数
         var moveY = y => {this.y = y;};
         moveX(x);
         moveY(y);
     }
};
point.moveTo(1, 1);
point.x; //==>1
point.y; //==>1

参考资料:
https://github.com/getify/You-Dont-Know-JS 你不知道的JS
http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html JavaScript的this用法
https://www.ibm.com/developerworks/cn/web/1207_wangqf_jsthis/ 深入浅出 JavaScript 中的 this
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this MDN this