返回

关于this

对于this,就是传递一个引用,从字面上我们就能理解,我保证你一地你更郁闷过为啥他在别人手里那么听话,在我手里永远那么叛逆。 要想玩好黑魔法,就必须深入地了解他。

一、为什么用this

老板:“给我去内个哪儿,找那谁把这个拿来” 实习:“您说哪(人)个(话)?” 老油条:“给您~” 看见没,this重要不:学好this,终生受用。

二、this是什么

this到底是一个什么样的机制,从之前的例子可以了解,**“这个”不是老板之前就声明好的,而是老板在说的时候绑定的,它取决于上下文和当时的情景。**如果不根据语境分析,你肯定理解不了。所以不跟据上下文就说this是指向自身,或者指向它的作用域都是错误的。this的绑定只取决于函数的调用位置。

三、调用位置

首先要区分函数调用和函数声明,最重要的是分析函数的调用栈,而且你要学会用调试工具去完成调用栈的分析。 用Chrome的调试工具查看调用栈:首先在你想要分析的地方设置断点,在断点停下来时,右侧调试区的 Call Stack 会显示当前断点所处的方法调用栈。Scope Variables 列表会显示此时局部变量和全局变量的值。、 另外,如果代码是压缩过的,你可以单击代码窗口左下角‘{}’按钮用于把杂乱的代码重新格式化为漂亮的代码。

五、绑定规则

1.默认绑定

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

当函数是直接使用不带任何修饰的函数引用进行调用的,就只能使用默认绑定,当然也没法用其他规则。 另外,需要注意的是在严格模式下,默认绑定无法绑定到全局对象(会绑定到undefined),无论函数在哪调用。

2.隐式绑定 如果调用位置有上下文就是隐式绑定。

function foo(){ console.log(this.a); }
var obj1={
a:2,
obj1:obj1
};
var obj2={
a:42,
foo:foo
};
obj1.obj2.foo(); // 2

foo的调用位置是在obj2,所以就在obj2中查找上下文。

隐式丢失 隐式绑定的常见问题是丢失绑定对象,不遵循隐式绑定那this也要指向一个东西,所以最后会遵循默认绑定,去寻找绑定对象。 再想一想之前为什么严格模式中默认绑定无法绑定全局对象,所以这样设计一定是有原因的,就是为了防止隐式丢失。

function foo(){ console.log(this.a); }
function dofoo(){ 
fn(); }  //实际调用位置(fn引用的是foo)
var obj1={
a:42,
foo:foo
};
var a='global'
dofoo(obj.foo) //'global'

这种情况我个人来看其实就是实际调用位置的上下文没有要绑定的对象a,所以只能遵循默认规则,去全局寻找。

3.显示绑定 如果我们不想在函数内部包含函数引用,而是直接在对象上调用函数,该怎么办? 我们可以用call()或apply()方法(注意:all()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组),所以之前我们的带么可以这样写: function foo(){ console.log(this.a); } var obj={ a:42 }; foo.call(obj); // 42 通过call我们显示地将obj当作this的绑定对象。 注意:在使用 call 和 apply 函数的时候要注意,如果传递的 this 值不是一个对象,JavaScript 将会尝试使用内部 ToObject 操作将其转换为对象。因此,如果传递的值是一个原始值比如 7 或 ‘foo’ ,那么就会使用相关构造函数将它转换为对象,所以原始值 7 通过new Number(7)被转换为对象,而字符串’foo’使用 new String(‘foo’) 转化为对象(来自MDN)。

为了解决之前的绑定丢失,我们需要再利用一个包裹函数,比如function bar(){ foo.call(obj); },以后无论如何调用bar,它总会在obj对象上调用foo方法。当然你嫌麻烦,也可以每次用foo是都写成foo.call(obj)。 这样不管怎么都感觉不那么优雅,所以ES5中内置了方法bind。bind()会返回一个新函数,在这个新函数中,this将永久地被绑定到了bind的第一个参数,所以我们就可以直接这么写var bar=foo.bind(obj);。

4.new绑定 再谈new绑定之前,我们首先要理解javascript中的new机制。 首先new运算符的作用是创建一个对象实例。这个对象可以是用户自定义的,也可以是带构造函数的一些系统自带的对象。 回想一下我们js面向对象时是如何创建对象的:

function Car(state) {
   this.state= make;
} //“构造函数”
car1=new Car(good);//我们就创造了一辆好车这个实例。

为什么构造函数要加引号,因为javascript中的构造函数只是使用new操作符是被调用的普通函数。 再看上面我们的创造对象的例子,我们的“构造函数”的this不正是与即将被创建的新对象绑定上了吗。

需要注意的是,new的“构造函数”返回(return)一个引用对象(数组,对象,函数等),都将覆盖new创建的匿名对象,如果返回(return)一个原始类型(无 return 时其实为 return 原始类型 undefined),那么就返回 new 创建的匿名对象。这也是new function(){}和function(){}() 的区别所在。

function C2(){
  this.a = 37; //被返回值覆盖
  return {a:38};
}

o = new C2();
console.log(o.a); // 38

六、优先级

通过名字我们就能猜出,显式>隐式>默认。我可以用一下代码在测试new和显式绑定的优先级。

function foo(e){ this.a=e; }
var obj={};
var bar=foo.bind(obj);

bar(2); //obj.a=2
var baz=new bar(3); //obj.a=2 baz.a=3

可以看出,new修改了this,将其从指向bar改到了baz,所以new更加强大。

至此,以后再遇到this,我们只要按照这四种规则机器优先级就能准确地找到this指的是谁了。

Licensed under CC BY-NC-SA 4.0